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

Improve docs on functions #5446

Merged
merged 5 commits into from
Jun 26, 2024
Merged

Conversation

danutsu
Copy link
Contributor

@danutsu danutsu commented Jun 20, 2024

While learning slint, I noticed that a lot of behaviors (in this case about functions) aren't clearly stated in the documentation. So I'm trying to document what I've learned and hopefully make it easier for the next person.

I've tested this mostly by experiment, meaning I'm not sure what's Working as Intended / by-design and what's Working as Implemented / by-chance. Actually that's a problem I'm hoping to address by documenting things more explicitly, but I need the core developers to tell me if something I've documented is not actually supposed to work that way.

Also, I don't know if you have specific standards/style regarding documentation that I should be following. If you do, let me know, I'm happy to read/apply them :)

There are more things I'd like to see in this file, in particular:

  • typical use cases for functions
  • more info/examples on how to use protected functions
  • differences between functions and callbacks and guidance on when to use each

But I don't feel I know enough about these topics myself yet, so I'm starting with this.

While learning to use slint, I noticed that a lot of behaviors (in this case about functions) aren't clearly stated in the documentation. So I'm trying to document what I've learned and hopefully make it easier for the next person.
Copy link
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot. This is definitively an improvement!

This introduces the notion of "target" which is kind of new in the documentation. I think "element name" might be a better term.

docs/reference/src/language/syntax/functions.md Outdated Show resolved Hide resolved
export component CallsFunction {
property <int> test: my-friend.double(1);

my-friend: HasFunction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
my-friend: HasFunction {
my-friend := HasFunction {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed!

docs/reference/src/language/syntax/functions.md Outdated Show resolved Hide resolved
@ogoffart
Copy link
Member

The CI is failling in the doctests test which checks that the slint snippets in the documentation are valid. You need to mark them as ignored, or they need to import the right types

failures:

---- docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_125 stdout ----
thread 'docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_125' panicked at /home/runner/work/slint/slint/target/debug/build/doctests-1e236a2bc24032cd/out/test_functions.rs:480:568:
called `Result::unwrap()` on an `Err` value: "Unknown type VerticalBox"
stack backtrace:
   0: rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::result::unwrap_failed
   3: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_125
   4: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_125::{{closure}}
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

---- docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_16 stdout ----
thread 'docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_16' panicked at /home/runner/work/slint/slint/target/debug/build/doctests-1e236a2bc24032cd/out/test_functions.rs:456:215:
called `Result::unwrap()` on an `Err` value: "Parse error: expected a top-level item such as a component, a struct, or a global"
stack backtrace:
   0: rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::result::unwrap_failed
   3: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_16
   4: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_16::{{closure}}
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

---- docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_78 stdout ----
thread 'docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_78' panicked at /home/runner/work/slint/slint/target/debug/build/doctests-1e236a2bc24032cd/out/test_functions.rs:468:347:
called `Result::unwrap()` on an `Err` value: "Syntax error: expected ';'"
stack backtrace:
   0: rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::result::unwrap_failed
   3: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_78
   4: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_78::{{closure}}
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

---- docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_97 stdout ----
thread 'docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_97' panicked at /home/runner/work/slint/slint/target/debug/build/doctests-1e236a2bc24032cd/out/test_functions.rs:474:412:
called `Result::unwrap()` on an `Err` value: "Syntax error: expected ';'"
stack backtrace:
   0: rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::result::unwrap_failed
   3: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_97
   4: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_97::{{closure}}
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

---- docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_40 stdout ----
thread 'docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_40' panicked at /home/runner/work/slint/slint/target/debug/build/doctests-1e236a2bc24032cd/out/test_functions.rs:462:611:
called `Result::unwrap()` on an `Err` value: "Unknown type Button"
stack backtrace:
   0: rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::result::unwrap_failed
   3: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_40
   4: doctests::docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_40::{{closure}}
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_125
    docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_16
    docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_40
    docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_78
    docsⳇreferenceⳇsrcⳇlanguageⳇsyntaxⳇfunctionsᐧmd::line_97

@danutsu
Copy link
Contributor Author

danutsu commented Jun 21, 2024

Thanks! I fixed the tests (hadn't noticed them) and I reorganized things a bit to make things clearer. I also added a section on name resolution inside the function code, because I was a bit unsure about it myself -- I could see it being implemented both ways!

Please take another look :)

Copy link
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the edits. Added a few comments.

Comment on lines 164 to 165
Functions, even marked public, cannot be exported and cannot be called from backend code (in Rust, C++,
JS, etc.). Declare a [callback](callbacks.md) which can call the function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function can be called from backend code. (invoke_...)
The difference with callback is that they cannot be replaced with a callback handler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I had no idea! The SampleGeneratedComponent docs in Rust don't include a function, and neither the C++ nor the JS docs mention that :) I'll update those too, but in a separate PR to keep this one manageable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, your point about the difference between this and callbacks made me take a stab at a "functions vs callbacks" section, could you look over it and let me know if I got anything wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, falling function from native code is missing from that documentation 🙈

Functions, even marked public, cannot be exported and cannot be called from backend code (in Rust, C++,
JS, etc.). Declare a [callback](callbacks.md) which can call the function.

## Name resolution in function code
Copy link
Member

@ogoffart ogoffart Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name resolution in function code is actually the same as in callback handler and property bindings (defined in #273 and arguably not well documented)

  1. function or callback arguments
  2. id of elements (including self, root, and parent)
  3. property or model/index name in scope.
  4. imported enums or globals
  5. builtin namespaces (Math, Colors, Key)
  6. Return specific lookup depending on the return type of a function / callback, or the type of a property binding. If this is an enum, then enum names are in scope, if this is a brush or color, then the Colors namespace is in scope.
  7. Builtin functions (from Math or Colors namespace, or debug or animation-tick)

I don't think two sections for name resolution are required

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, OK. I removed the second section. I think it would make sense to document this somewhere in the .slint docs to make it more discoverable -- of course it doesn't make sense to add it to the functions doc -- I'm thinking somewhere more specific, like a separate page under syntax? (then functions, callbacks, properties, etc. could all link to it). The github issue is very hard for anyone to discover :)

I can do that if you want (but also in a separate PR, keeping this one focused)

@danutsu
Copy link
Contributor Author

danutsu commented Jun 24, 2024

Thank you, please take another look 😄

Copy link
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@tronical what do you think?

- Callback visiblity is always similar to `public` functions

In general, the biggest reason to use callbacks is to be able to handle them from the backend code. If
that is not needed, using a callback or a function is a matter of personal choice.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's a matter of personal choice. If assigning a different handler is not needed, it's better to use a function than a callback.

(Function were introduced in a later version of Slint than callback, which is by maybe some example still use callback when they could have used functions)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of thought so myself, but didn't feel comfortable saying this without input from the core team. Updated to say "use a function" :)

Copy link
Member

@tronical tronical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of suggestions inline. Overall I think this is an improvement, but I wouldn't have put so much emphasis on the lookup.

Comment on lines 4 to 5
Similarly to other programming languages, functions in Slint are way to name, organize and reuse
a piece of logic/code.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that should be an adjective, not adverb?

Suggested change
Similarly to other programming languages, functions in Slint are way to name, organize and reuse
a piece of logic/code.
Similar to other programming languages, functions in Slint are way to name, organize, and reuse
a piece of logic/code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 7 to 8
Functions can be defined as part of a component, or as part of an element within a component. Functions
are always part of a component: it is not possible to declare global (top-level) functions, or to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the "Functions are always part of a component" bit is redundant.

Suggested change
Functions can be defined as part of a component, or as part of an element within a component. Functions
are always part of a component: it is not possible to declare global (top-level) functions, or to
Functions can be defined as part of a component, or as part of an element within a component. It is not possible to declare global (top-level) functions, or to

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, good point.

declare them as part of a struct or enum. It is also not possible to nest functions within other
functions.

## Declaring a function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Match the first sentence, that uses plural. An alternative would be to use singular for both.

Suggested change
## Declaring a function
## Declaring functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

There are a lot of similarities between functions and [callbacks](callbacks.md):

- They are both callable blocks of logic/code
- They can be invoked similarly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't they invoked not only similarly, but in exactly the same way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done -- see, as someone just learning Slint, I'm a little bit afraid of making calls like that. As far as I know, they are, but I'm not an expert, so this was just me hedging 😛

- They can both have parameters and return values
- They can both be declared `pure`

But there are also key differences:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My immediate question here would be: If these are the key differences, where can I read about the remaining differences? I suggest to leave that out for now :)

Suggested change
But there are also key differences:
But there are also differences:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! (to be fair, "what else is different" is also a question I have and I honestly don't know the answer to it)

Comment on lines 185 to 186
In general, the biggest reason to use callbacks is to be able to handle them from the backend code. If
that is not needed, we recommend using a function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor simplification:

Suggested change
In general, the biggest reason to use callbacks is to be able to handle them from the backend code. If
that is not needed, we recommend using a function.
In general, the biggest reason to use callbacks is to be able to handle them from the backend code. Use
a function if that is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@danutsu
Copy link
Contributor Author

danutsu commented Jun 25, 2024

I wouldn't have put so much emphasis on the lookup.

The lookup rules were quite surprising to me as someone unfamiliar with Slint, and I'd say they are not very typical. I can't, off the top of my head, remember another language that works this way. If there was a page describing the lookup rules in general (perhaps I'll try to add that based on @ogoffart's comment) then yeah, this could be simplified :)

Thank you for the review 👍 -- let me know if anything else is needed before merging!

@tronical tronical merged commit d126c28 into slint-ui:master Jun 26, 2024
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants