Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convenience syntax for helper functions #174

Closed
tronical opened this issue Feb 15, 2021 · 11 comments
Closed

Convenience syntax for helper functions #174

tronical opened this issue Feb 15, 2021 · 11 comments
Labels
a:compiler Slint compiler internal (not the codegen, not the parser) breaking change Anything that probably require a semver bump enhancement New feature or request rfc Request for comments: proposals for changes

Comments

@tronical
Copy link
Member

At moment it's possible to use callback handlers to define what can function as helper functions:

  callback helper(int) -> logical_length;
    helper(idx) => {
        idx * 30px
    }

    Rectangle {
        background: green;
        y: helper(1);
    }

This works well in the sense that the callback helper declaration acts like it is declaring a property and the handler declaration assigns the right body to it. It would be great if the parser would support a more convenient syntax to achieve the same semantical result:

function blah(idx: int) -> logical_length {
    idx * 30
}
@tronical tronical added enhancement New feature or request a:compiler Slint compiler internal (not the codegen, not the parser) labels Feb 15, 2021
@tronical tronical added this to Needs triage in Issue priorities via automation Feb 15, 2021
@ogoffart
Copy link
Member

We could also try to have a way to "initialize" inline

// no arguments
callback foo => { /*...*/ }
// with arguments
callback foo(abc: int) -> int => { /* ... */ }

Or is this => too much?


We also need to consider the side effects an threading issues of these function.

I think we have the following dimention for different kind of function or callback:

  1. Side effect: property binding should in theory be "pure" so they should not be allowed to call event or things that may result. This can only be enforced if we know what has side effect.
  2. synchronous: anything that returns a value for example need to be synchronous. But we could also imaging events which would have effect asynchronously because it is run in a thread for example.
  3. Can it be replaced by native code?
  4. Can we connect several handler to it, or only one handler.

Maybe we can use these keywords

No side effect side effect allowed
synchronous, 1 handler, can return value function callback
possibly async, possibly several handler N/A event

@tronical
Copy link
Member Author

Or is this => too much?

IMO yes. I'd prefer function because that's what it is. If that comes with additional restrictions then I think that's fine, such as a check for side-effects.

I like the proposed table though, that's nice.

@tronical tronical moved this from Needs triage to Low priority in Issue priorities Apr 19, 2021
@ogoffart ogoffart added the breaking change Anything that probably require a semver bump label May 3, 2021
@ogoffart
Copy link
Member

@tronical and I discussed this today.

We realized that there is many dimension to the problem of declaring a callback:

  1. Function that can be override (set) by native code or when instantiating (callback) or not (helper functions)
  2. Whether a function has a side effect or is pure. (The goal being to check, at compile time, that property bindings only use pure things)
  3. internal or public
  4. Synchrinous or queues (this could actually be specified when connecting to the callback. eg: clicked => queued { debug("foo") }

We haven't agreed on a good way to declare purity and if it should be the default and enforced. (@pure attribute or public pure function, or pure by default and public impure function or side-effect)

But we agreed that there is a need for non over-writable function such as

component CheckBox {
   public function toggle() {   // private by default
      self.checked = !self.checked;
      self.toggled(self.checked);
   }
   callback toggled(checked : bool); // callbacks are always public
}

For callback, we decided to

  • Add optional name on argument of callback declaration: callback clicked(buttons: int);
  • Add optional type on argument of callback connection : clicked(buttons: int) => { ... }

@Be-ing
Copy link
Contributor

Be-ing commented Nov 19, 2022

But we agreed that there is a need for non over-writable function such as

To clarify what you mean, in that example toggle would be callable from outside CheckBox but outside code could not assign toggle to some other function?

@tronical
Copy link
Member Author

Our current thinking is this:

  • By default, public functions and all callbacks are impure.
  • Private functions have "auto" purity (we infer).
  • callback or public functions can be annotated as pure /

If you want to call a function from a binding or pure function, you then need to declare it pure. Later, if you
want to add a side-effect to the pure function, you end up having to remove the "pure" declaration from the public
function and chances are that you might see that such a change will affect your users.

@ogoffart
Copy link
Member

PR that implements it: #1989

@ahayzen-kdab
Copy link

Elsewhere in Rust the default is the "safe" or "better" option and you have to opt-in to the other option, eg by using unsafe or mut for safety and mutability. This makes me wonder if where possible things should be pure by default and you should have to opt-in to impure 🤔 But maybe I'm overthinking things and this would be too much boilerplate to mark most functions as impure :-)

@ogoffart
Copy link
Member

We indeed are wondering if the default should be pure or impure.
I think for callback it should be impure as most callbacks are meant to be impure. (Unless they return a value, maybe)

For function it is a bit less clear what the default should be.

Making it a different default based on the return value doesn't sound like a good idea.

That said, it is not clear what in pure or impure is the "safer" or "better". The equivalent of pure in rust would be const fn which is not the default

@ahayzen-kdab
Copy link

Right, so maybe having a side affect isn't strong enough reason to choose between pure/impure by default. And as you say for functions Rust doesn't use const fn anyway 🤔

Reading your blog there is the example of calling a function that has a side affect during property evaluation.

  height: { 
      toggle(); // BAD: this changes another property during evaluation.
      return compute_size(42px);
  }

Once there are pure/impure, are you thinking here that the binding would need to have something tagged onto or an unsafe block to indicate that the developer really does want to call an impure function ? such as unsafe { toggle(); } or tag the whole property binding or something ? eg what would this look like with the pure/impure stuff ?

@ogoffart
Copy link
Member

Based on the experiment in #1989 , I noticed that, after porting the examples to annotate the callback with pure, only two testcases do suspicious things by making use of side effects in properties. These tests are arguably not nice code and i don't know if their use case should be supported.
I'm pretty sure however that people might rely in such hack like the ones in #112 (comment)
However, if it involve native code, they can still mark the callback as pure and have the native code violate that. We don't protect against it
So i don't know if we need a way to tell .slint that we want to do side effect in a pure function. If so, unsafe { } is not the right keyword. Maybe side-effect { toggle() } or toggle!() or 😷toggle()

@ogoffart ogoffart added the rfc Request for comments: proposals for changes label Dec 27, 2022
@ogoffart
Copy link
Member

Done

Issue priorities automation moved this from Low priority to Closed Jan 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:compiler Slint compiler internal (not the codegen, not the parser) breaking change Anything that probably require a semver bump enhancement New feature or request rfc Request for comments: proposals for changes
Projects
Development

No branches or pull requests

4 participants