Lambdas¶
If you want to write reusable blocks of code, use a segment. However, sometimes you need to create a highly application-specific callable that can be passed as argument to some function or returned as the result of a segment. We will explain this concept by filtering a list. Here are the relevant declarations:
class IntList {
fun filter(filterFunction: (element: Int) -> shouldKeep: Boolean) -> filteredList: IntList
}
fun intListOf(elements: List<Int>) -> result: IntList
First, we declare a class IntList
, which has a single method called filter
. The filter
method returns a single result called filteredList
, which is a new IntList
. filteredList
is supposed to only contain the elements of the receiving IntList
for which the filterFunction
parameter returns true
.
Second, we declare a global function intListOf
that is supposed to wrap elements
into an IntList
.
Say, we now want to keep only the elements in the list that are less than 10
. We can do this by declaring a segment:
Here is how to solve the task of keeping only elements below 10
with this segment:
The call to intListOf
is just there to create an IntList
that we can use for filtering. The interesting part is the argument we pass to the filter
method, which is simply a reference to the segment we declared above.
The problem here is that this solution is very cumbersome and verbose. We need to come up with a name for a segment that we will likely use only once. Moreover, the segment must declare the types of its parameters and its results in its header. Finally, the declaration of the segment has to happen in a separate location then its use. We can solve those issues with lambdas.
Block Lambdas¶
We will first rewrite the above solution using a block lambda, which is essentially a segment without a name and more concise syntax that can be declared where it is needed:
While this appears longer than the solution with segments, note that it replaces both the declaration of the segment as well as the reference to it.
Here are the syntactic elements:
- A list of parameters, which is enclosed in parentheses. Individual parameters are separated by commas.
- The body, which is a list of statements enclosed in curly braces. Note that each statement is terminated by a semicolon.
The results of a block lambda are declared in its body using assignments.
Declare Results of Block Lambdas¶
Similar syntax is used to yield results of block lambdas. The difference to segments is that block lambdas do not declare their results in their header. Instead the results are declared within the assignments, just like placeholders. The block lambda in the following snippet has a single result called greeting
, which gets the value "Hello, world!"
:
The assignment here has the following syntactic elements:
- The keyword
yield
, which indicates that we want to declare a result. - The name of the result, here
result
. This can be any combination of upper- and lowercase letters, underscores, and numbers, as long as it does not start with a number. However, we suggest usinglowerCamelCase
for the names of results. - An
=
sign. - The expression to evaluate (right-hand side).
- A semicolon at the end.
Expression Lambdas¶
Often, the body of a block lambda only consists of yielding a single result, as is the case in the example above. The syntax of block lambdas is quite verbose for such a common use-case, which is why Safe-DS has expression lambdas as a shorter but less flexible alternative. Using an expression lambda we can rewrite the example above as
These are the syntactic elements:
- A list of parameters, which is enclosed in parentheses. Individual parameters are separated by commas.
- An arrow
->
. - The expression that should be returned.
Closures¶
Note: This is advanced concept, so feel free to skip this section initially.
Both block lambdas and expression lambdas are closures, which means they remember the values of placeholders and parameters that can be accessed within their body at the time of their creation. Here is an example:
This deserves further explanation: We declare a segment lazyValue
. It takes a single required parameter value
with type Int
. It produces a single result called result
, which has a callable type that takes no parameters and produces a single result called storedValue
with type Int
. In the body of the segment we then assign an expression lambda to the result result
.
The interesting part here is that we refer to to the parameter value
within the expression of the lambda. Since lambdas are closures, this means the current value
is stored when the lambda is created. When we later call this lambda, exactly this value is returned.
Restrictions¶
At the moment, lambdas can only be used if the context determines the type of its parameters. Concretely, this means we can use lambdas in these two places:
- As an argument that is assigned to a parameter with a type in a call.
- As the value that is assigned to a result of a segment. In other cases, declare a segment instead and use a reference to this segment where you would write the lambda.