-
Notifications
You must be signed in to change notification settings - Fork 1
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
Add documentation for lambdas. #498
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,10 +1,21 @@ | ||||||
# Blocks | ||||||
# Blocks and Lambdas | ||||||
|
||||||
Blocks are a mechanism to conveniently and efficiently provide callbacks to | ||||||
functions. This mechanism is, for example, used for the methods `int.repeat` | ||||||
and `List.do`. | ||||||
Toit provides two mechanisms for creating and passing around pieces of executable | ||||||
code: blocks and lambdas. While they share some similarities, they have distinct | ||||||
characteristics, use cases, and performance implications. | ||||||
|
||||||
``` | ||||||
## Introduction to Blocks and Lambdas | ||||||
|
||||||
### Blocks | ||||||
|
||||||
Blocks are a highly efficient mechanism to provide callbacks to functions. They are | ||||||
introduced with a colon `:` and are commonly used in the core libraries, for example | ||||||
with `int.repeat` and `List.do`. Blocks are very efficient and should be used | ||||||
liberally. | ||||||
|
||||||
Example of a block: | ||||||
|
||||||
```toit | ||||||
main: | ||||||
// Print the numbers from 1 to 10, one per line. | ||||||
10.repeat: | ||||||
|
@@ -16,13 +27,44 @@ main: | |||||
``` | ||||||
|
||||||
Here we used the automatic block argument, `it`, [see | ||||||
below](#block-arguments) | ||||||
below](#block-arguments). | ||||||
|
||||||
Syntactically `repeat` and `do` look like they are built-in to the language | ||||||
like `for` and `if` are, but they are actually normal methods that use the | ||||||
block feature. | ||||||
|
||||||
## Basic use of blocks | ||||||
### Lambdas | ||||||
|
||||||
Lambdas are more flexible function-like objects introduced with a double colon `::`. | ||||||
They can be stored in variables, passed as arguments, and returned from functions. | ||||||
While not as efficient as blocks, lambdas offer greater versatility. | ||||||
|
||||||
Example of a lambda: | ||||||
|
||||||
```toit | ||||||
printer/Lambda? := null | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a top-level variable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to show that it can be stored in places where a block can't be. |
||||||
main: | ||||||
printer = :: print it | ||||||
[1, 2, 3].do: printer.call it | ||||||
``` | ||||||
|
||||||
## Key Differences | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
1. **Syntax**: Blocks use `:`, lambdas use `::`. | ||||||
2. **Return behavior**: Blocks can have non-local returns, lambdas cannot use `return` at all. | ||||||
3. **Lifetime**: Blocks are tied to the current context and cannot outlive it. Lambdas can be stored and used later. | ||||||
4. **Flexibility**: Lambdas can be stored in variables, fields, and collections, and returned from functions. Blocks cannot. | ||||||
5. **Efficiency**: Blocks are significantly more efficient than lambdas. | ||||||
|
||||||
## Blocks in Detail | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
### Efficiency of Blocks | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
Blocks are designed to be highly efficient in Toit. They are stack-allocated, | ||||||
which means they have minimal overhead in terms of memory usage and execution time. | ||||||
This efficiency makes blocks the preferred choice for most callback scenarios. | ||||||
|
||||||
### Basic use of blocks | ||||||
|
||||||
Blocks are used extensively in the Toit standard library, so you need to know | ||||||
the basics of calling functions that accept blocks. | ||||||
|
@@ -53,7 +95,7 @@ main: | |||||
4.repeat: print "again" | ||||||
``` | ||||||
|
||||||
## Block arguments | ||||||
### Block arguments | ||||||
|
||||||
Blocks often receive arguments when they are called as callbacks. For instance, | ||||||
`List.do` iterates over a list and calls the block with every element. When the | ||||||
|
@@ -120,13 +162,13 @@ If a block accepts multiple arguments, but you only need to refer to the first, | |||||
then you can omit the naming and just use `it`. If there are parameters that | ||||||
you don't need, then you can use a wildcard `_` instead of giving them a name. | ||||||
|
||||||
## Returning from blocks and accessing variables | ||||||
### Returning from blocks and accessing variables | ||||||
|
||||||
Blocks look like they are built-in which makes it important that they also act | ||||||
like they are built-in. This means that returning from a block with `return`, | ||||||
or accessing local variables must just work. | ||||||
|
||||||
### Non-local return | ||||||
#### Non-local return | ||||||
|
||||||
The non-local return is the most common return from blocks. It is best explained based on an example: | ||||||
|
||||||
|
@@ -157,7 +199,7 @@ doesn't just return from the block or `do`, it returns from `first-negative`. | |||||
This means that we can short circuit the search when we find the first negative | ||||||
number. | ||||||
|
||||||
### Local return | ||||||
#### Local return | ||||||
|
||||||
In some situations, it is useful to do a local return. That is, to just return | ||||||
from the block. To do this, you use the `continue` keyword followed by a dot | ||||||
|
@@ -190,7 +232,7 @@ Here the block given to `map` uses `continue` to do a local return of an odd | |||||
value when it sees an even value. The snippet `continue.map` can be read as | ||||||
"continue in the `map` function". | ||||||
|
||||||
### Default return | ||||||
#### Default return | ||||||
|
||||||
The default return of a block is a local return. The block returns the value of | ||||||
the last statement in the block. For example | ||||||
|
@@ -362,7 +404,7 @@ main: | |||||
|
||||||
Now the intend is clear: you are `do`'ing something to the list which corresponds to visiting each element of the list. | ||||||
|
||||||
## Calling functions with named block parameters | ||||||
### Calling functions with named block parameters | ||||||
|
||||||
Block parameters can also be named. This is indicated in the function signature | ||||||
by enclosing the named parameter in brackets `[ ]` (the same as for other block | ||||||
|
@@ -431,7 +473,7 @@ Unable to find 4 | |||||
|
||||||
Here we have formatted the arguments differently, so each of the block arguments are easily identified. | ||||||
|
||||||
## Blocks as values | ||||||
### Blocks as values | ||||||
|
||||||
Blocks are values in Toit which is why functions accept them as arguments. A | ||||||
block value is defined as a colon `:` followed by the body of the block which | ||||||
|
@@ -459,24 +501,89 @@ Syntactically, it looks a bit cryptic when blocks are stored in local | |||||
variables. However, the main purpose of blocks is to pass snippets of code to | ||||||
functions. | ||||||
|
||||||
Blocks aren't just "normal" functions as in other programming languages. They | ||||||
are much more efficient, but also come with some restrictions. | ||||||
|
||||||
### Restrictions | ||||||
#### Restrictions | ||||||
|
||||||
Blocks are stack allocated, which is what makes them so efficient, but it also limits where they can be used: | ||||||
|
||||||
- Blocks cannot be stored in instance fields, static class fields, and globals. | ||||||
|
||||||
- Blocks cannot be stored in any collection. | ||||||
|
||||||
- Blocks cannot be returned from functions and methods. | ||||||
- Lambdas cannot capture blocks. | ||||||
- Blocks cannot take blocks as arguments. | ||||||
|
||||||
- [Lambdas](../tasks) cannot capture blocks. | ||||||
## Lambdas in Detail | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
- Blocks cannot take blocks as arguments. | ||||||
### Basic Use of Lambdas | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
Lambdas are more versatile than blocks and can be used in a wider variety of | ||||||
situations, though at a cost of reduced efficiency compared to blocks: | ||||||
|
||||||
```toit | ||||||
start-task: | ||||||
task:: | ||||||
3.repeat: | ||||||
print "still running" | ||||||
sleep --ms=300 | ||||||
|
||||||
main: | ||||||
start-task | ||||||
print "task started" | ||||||
``` | ||||||
|
||||||
In this example, we define a lambda that runs as a separate task, continuously | ||||||
printing a message. While less efficient than a block, a lambda is necessary here | ||||||
due to its ability to be stored and executed later. | ||||||
|
||||||
### Storing and Reusing Lambdas | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
Unlike blocks, lambdas can be stored in variables, fields, and collections: | ||||||
|
||||||
## Defining functions that take block arguments | ||||||
```toit | ||||||
class WatchedBox: | ||||||
value_/any := null | ||||||
callbacks_/List ::= [] | ||||||
|
||||||
watch callback/Lambda: | ||||||
callbacks_.add callback | ||||||
|
||||||
value -> any: return value_ | ||||||
value= new-value: | ||||||
value_ = new-value | ||||||
callbacks_.do: it.call new-value | ||||||
|
||||||
add-printer box/WatchedBox: | ||||||
box.watch:: print "Was changed to $it." | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I would drop the trailing period here (and in the comments). It just seems misplaced and we don't typically have that in debug prints. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
main: | ||||||
box := WatchedBox | ||||||
add-printer box | ||||||
box.value = 499 // Prints "was changed to 499." | ||||||
box.value = 42 // Prints "was changed to 42." | ||||||
``` | ||||||
|
||||||
This example demonstrates how lambdas can be stored in a list | ||||||
(`callbacks_`) and called later. | ||||||
|
||||||
### Lambda Arguments | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
Like blocks, lambdas can take arguments. If a lambda takes a single argument, | ||||||
you can use `it` as the default parameter name: | ||||||
|
||||||
```toit | ||||||
main: | ||||||
multiplier := :: it * 2 | ||||||
print (multiplier.call 21) // Prints 42. | ||||||
``` | ||||||
|
||||||
For multiple arguments, you need to declare them explicitly: | ||||||
|
||||||
```toit | ||||||
main: | ||||||
adder := :: | a b | a + b | ||||||
print (adder.call 20 22) // Prints 42. | ||||||
``` | ||||||
|
||||||
## Defining functions that take callbacks | ||||||
|
||||||
You can define your own functions that accept block arguments. You have already | ||||||
seen what the function signature should look like: block arguments must be | ||||||
|
@@ -495,3 +602,42 @@ class int: | |||||
``` | ||||||
|
||||||
Note: non-named block parameters have to be the last in the function signature. | ||||||
|
||||||
Similarly, you can define a function that takes a lambda as an argument: | ||||||
|
||||||
``` | ||||||
call-lambda callback/Lambda: | ||||||
callback.call 42 | ||||||
|
||||||
main: | ||||||
call-lambda:: print it | ||||||
``` | ||||||
|
||||||
A lambda is an object of type `Lambda`. You can store it in a variable, pass it | ||||||
as an argument, and call it later. The `call` method is used to execute the | ||||||
lambda. | ||||||
|
||||||
### Lambdas vs. blocks: choosing the right tool | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
|
||||||
Only the receiver of a callback can determine whether to use a block or a lambda. As | ||||||
a user, one has to follow the type declared in the method signature. | ||||||
|
||||||
When writing functions that accept callbacks, consider the following guidelines: | ||||||
- Use blocks for short-lived, context-dependent callbacks that don't need to be | ||||||
stored or returned. Blocks are significantly more efficient and should be the | ||||||
default choice when possible. | ||||||
- Use lambdas when you need to store the function. | ||||||
- Consider providing both block and lambda versions if you need to support both | ||||||
use cases. The lambda version can always redirect to the block version: | ||||||
``` | ||||||
my-function callback: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe flip the order of these two functions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
my-function: callback.call it | ||||||
|
||||||
my-function [block]: | ||||||
block.call 42 | ||||||
|
||||||
main: | ||||||
// Both of these will print 42. | ||||||
my-function:: print it | ||||||
my-function: print it | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels more logical to start with the block-variant call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -566,7 +566,7 @@ for each element using block structure: | |||||
Here the statement is on a single line, so there is no need to use indentation. | ||||||
When using `names.do`, a method available on all | ||||||
[collections](./listsetmap), the [special variable | ||||||
`it`](./blocks#block-arguments) contains the individual elements from the list in | ||||||
`it`](./blocks-and-lambdas#block-arguments) contains the individual elements from the list in | ||||||
turn. If there are 5 elements in the `names` list, we will call `print` 5 times | ||||||
producing 5 separate lines of output. | ||||||
|
||||||
|
@@ -778,7 +778,7 @@ have used: | |||||
print "Hello, $names[i]" | ||||||
``` | ||||||
|
||||||
## Blocks | ||||||
## Blocks and lambdas | ||||||
|
||||||
We already saw the `repeat` method on integers and the `do` method on lists: | ||||||
|
||||||
|
@@ -794,8 +794,7 @@ main: | |||||
``` | ||||||
|
||||||
Syntactically they look like they are built in to the language like `if` and | ||||||
`for`, but they are actually perfectly normal methods on the List and Integer | ||||||
classes: | ||||||
`for`, but they are actually normal methods on the List and int classes: | ||||||
|
||||||
<!-- SKIP CODE --> | ||||||
|
||||||
|
@@ -805,23 +804,60 @@ class List: | |||||
do [block]: | ||||||
size.repeat: block.call this[it] | ||||||
|
||||||
class Integer: | ||||||
class int: | ||||||
// ... | ||||||
repeat [block]: | ||||||
for i := 0; i < this; i++: | ||||||
block.call i | ||||||
``` | ||||||
|
||||||
They are making use of a feature called [blocks](./blocks). These are | ||||||
They are making use of a feature called [blocks](./blocks-and-lambdas). These are | ||||||
snippets of code that can be passed down the stack as arguments to methods and | ||||||
functions. At the call site we precede the block with a colon, '`:`', and at | ||||||
the function definition we surround the parameter name with square brackets, | ||||||
'`[]`'. Often, there is one block parameter, it is in the final position and | ||||||
it is called `block`. | ||||||
|
||||||
## Blocks that return a value | ||||||
If a callback needs to survive the scope in which it is defined, we can't use | ||||||
blocks. Instead, we can use [lambdas](./blocks-and-lambdas#lambdas): | ||||||
|
||||||
A block can return a value each time it is run. This is used for example in the `filter` method on lists. | ||||||
``` | ||||||
// Returns a function that adds n to its argument. | ||||||
add-n n: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||||||
return (:: it + n) | ||||||
``` | ||||||
|
||||||
The syntax for lambdas is the same as for blocks, except that the we use `::` | ||||||
instead of `:`. The lambda above is a function that takes a single argument and | ||||||
returns the sum of that argument and `n`. We can use it like this: | ||||||
|
||||||
``` | ||||||
main: | ||||||
add-5 := add-n 5 | ||||||
print (add-5.call 10) | ||||||
``` | ||||||
|
||||||
This will print `15`. | ||||||
|
||||||
## Blocks and lambdas with multiple arguments | ||||||
|
||||||
Blocks and lambdas can have parameters, just like methods. The parameters are | ||||||
listed after the `::` or `:`. Here is an example of a block with two parameters: | ||||||
|
||||||
``` | ||||||
main: | ||||||
map := { "Lars": 1, "Kasper": 2 } | ||||||
map.do: | key value | | ||||||
print "$key has value $value" | ||||||
``` | ||||||
|
||||||
The block in the `do` method has two parameters, `key` and `value`. The `do` | ||||||
method will call the block with each key-value pair in the map. | ||||||
|
||||||
## Blocks and lambdas that return a value | ||||||
|
||||||
A block or lambda can return a value each time it is run. This is used for example | ||||||
in the `filter` method on lists. | ||||||
|
||||||
``` | ||||||
// Takes a list of words, and returns a new list with only the | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.