4.1 Functions
A function is normally written with the fun definition form or fun expression form, but see also Callable.
definition | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
definition | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
definition | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
definition | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
definition | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
|
The first case for the definition form shown above is subsumed by the second case, but it describes the most common form of function definition.
> f(0)
1
List.length(l)
When | is not used, then arguments can have default values as specified after a = or in a block after the argument name. Bindings for earlier arguments are visible in each default_expr or default_body, but not bindings for later arguments; accordingly, matching actions are interleaved with binding effects (such as rejecting a non-matching argument) left-to-right, except that the result of a default_expr is subject to the same constraints imposed by annotations and patterns for its argument as an explicitly supplied argument would be. An argument form keyword or keyword = default_expr is equivalent to the form keyword: id or keyword: id = default_expr where id is composed from keyword by taking its string form and lexical context. A :: or :~ is not allowed in default_expr, unless it is nested in another term, since that might be misread or confused as an annotation in bind for an identifier; for similar reasons, bind and default_expr cannot contain an immediate =.
A constraint not reflected in the grammar is that all optional by-position arguments must follow all required by-position arguments. To define a function that works otherwise, use the | form.
> f(0)
[0, 1]
> f(0, 2)
[0, 2]
> transform([1, 2])
[1, 2]
> transform([1, 2], ~dx: 7)
[8, 2]
> transform([1, 2], ~dx: 7, ~scale: 2)
[9, 4]
> valid(2)
3
> valid(3, 2)
5
When alternatives are specified with multiple | clauses, the clauses can be provided immediately after fun or after the name and a maybe_res_annot as described further below.
> hello("World")
"Hello, World"
> hello("Inigo", "Montoya")
"Hello, Inigo Montoya"
> is_passing(80) && is_passing(#true)
#true
When a rest sequence contains & list_bind or repet_bind , ..., then the function or function alternative accepts any number of additional by-position arguments. For & list_bind, the additional arguments are collected into a list value, and that list value is bound to the list_bind. Static information associated by List is propagated to list_bind. For repet_bind , ..., each variable in repet_bind is bound to a repetition that repeats access to that piece of each additional argument. Only one by-position rest binding, & list_bind or repet_bind , ..., can appear in a rest sequence.
When a rest sequence contains ~& map_bind, then the function or function alternative accepts any number of additional keyword arguments. The additional keywords and associated argument values are collected into an immutable map value to be bound to map_bind. Static information associated by Map is propagated to map_bind. Only one ~& map_bind can appear in a rest sequence.
#true
| is_sorted([head, next, & tail]):
> is_sorted([1, 2, 3, 3, 5])
#true
> is_sorted([1, 2, 9, 3, 5])
#false
#true
| is_sorted([head, next, tail, ...]):
> is_sorted([1, 2, 3, 3, 5])
#true
> is_sorted([1, 2, 9, 3, 5])
#false
When maybe_res_annot is present, it provides an annotation for the function’s result, but only for the corresponding case if a maybe_res_annot is present in a multi-case function written with |. In the case of a checked annotation using ::, the function’s body is not in tail position with respect to a call to the function, since a check will be applied to the function’s result. When maybe_res_annot is present for a function declared with cases afterward, a maybe_res_annot applies to all cases, in addition to any maybe_res_annot supplied for a specific case. A maybe_res_annot that has a parenthesized sequence of annots (with our without values) describes multiple result values with an annotation for each individual result.
> hello("World")
"Hello, World"
> hello()
hello: result does not satisfy annotation
result: #false
annotation: String
> things_to_say()
"Hi"
"Bye"
> things_to_say("Nachos")
things_to_say: results do not satisfy annotation
results...:
"Hi"
"Bye"
"Nachos"
annotation: (String, String)
When ~doc is present as an option and fun is used in a declaration context, then a doc submodule splice is generated with rhombus/doc as the submodule’s language. In the submodule, id_name is defined as a DocSpec value to record the function’s arguments and result annotation (if any). If a maybe_res_annot is present with :~, it is converted to :: in the recorded function shape. Tools such as Rhombus Scribble can import the submodule to extract the recorded information.
When ~name is present as an option or name_option, the given op_or_id_name is used for reporting annotation failures on arguments and results, and it is also used when printing the function. Otherwise, id_name is used. Supplying ~name does not change the name that is bound, which is always the initial id_name.
When ~who is present as an option,
name_option, or case_option, the given id
is bound to a symbol form of the function name—
> trivial(1)
1
> trivial
#<function:easy>
> trivial("one")
easy: argument does not satisfy annotation
argument: "one"
annotation: Int
> trivial(0)
easy: zero
When ~unsafe is present as a case_option, an alternative implementation is provided that may be used for calls to the function in an unsafe context (see use_unsafe). The alternative implementation as a unsafe_body sequence is intended to produce the same result as the case’s main body sequence, but it may skip annotation checks. Annotation on the function arguments are not checked (and no conversions are applied) for unsafe calls, even when the annotation is accoated with an argument via :: as opposed to :~. The unsafe_body sequence itself is in unsafe mode. Argument bindings are restricted when a ~unsafe option is present: the binding must be either an immediate identifier, an identifier annotated with :: or :~, or a single-identifier binding form that accepts any argument, such as _. Keyword are allowed only when a function does not have multiple cases, and & or ~& arguments are not allowed. (A final repetition argument using ellipsis is allowed.) When a multi-case function has ~unsafe for any case, then it must have ~unsafe for all cases. If the function has a result annotation, it is not checked (and no conversion is applied) for the unsafe result.
~unsafe:
// assume all fixnums and that result fits:
// use generic arithmetic, check fixnum result at end:
res + n
> fixnum_sum([1, 2, 3])
6
> fixnum_sum([1, 2, 3 ** 100]) // seriously bad in unsafe mode
fixnum_sum: argument does not satisfy annotation
argument: [1, 2, 515377520732011331036461129765621272702107522001]
annotation: List.of(Fixnum)
expression | |||||||
| |||||||
| |||||||
expression | |||||||
| |||||||
| |||||||
expression | |||||||
| |||||||
| |||||||
expression | |||||||
|
The first case shown for the expression form above is subsumed by the second case, but it describes the most common forms of function expression.
> identity(1)
1
> curried_add(1)(2)
3
Optional and keyword argument support for a fun expression is the same as for a fun. The fun expression form does not support a ~doc option.
See also _ for information about function shorthands using _. For example, (_ div _) is a shorthand for fun (x, y): x div y.
A binding as an entry point allows a form to work and cooperate with contexts such as constructor that syntactically require a function. That is, an entry point is a syntactic concept. Its corresponding run-time representation is normally a function, but an entry point may need to be manipulated statically, such as adding an extra argument to make it serve as a method. Besides fun, the macro form is also bound as entry point.
A binding as an immediate callee allows a form to work and cooperate with contexts such as the right-hand side of the |> operator to improve static-information propagation.
method | ||
| ||
| ||
method | ||
|
> Function.map((_ + _), [1, 2, 3], [4, 5, 6])
[5, 7, 9]
[5, 7, 9]
1
2
3
In a call Function.pass(arg, ...), if every arg is either a simple expression or repetition, and if the implicit #%call form is the default #%call, no intermediate lists are produced. This makes Function.pass suitable for “side-effecting” repetitions, as an alternative to for.
> Function.pass()
"a",
~weird: "call",
& ["these", "are", "ignored"],
"away",
)
> block:
// a good alternative to `for`
Function.pass(println(x), ..., ...)
1
2
3
4
5
method | |||||||
| |||||||
method | |||||||
|
A bitwise by_pos_mask for the number(s) of by-position arguments that a function accepts. If the function accepts n arguments, then by_pos_mask bits.and (1 bits.(<<) rhombus(n, ~var)) is non-zero.
A required_kws list of keyword arguments that the function requires, where the keywords are sorted by <.
An allowed_kws as #false if the function accepts any keyword, or a list of keyword arguments that the function accepts, including the required keywords, where the keywords are sorted by <.
The Function.arity method returns a function that is like f, but with its allowed arguments more restricted. The given by_pos_mask must have only bits that are set of the mask of f, the given required_kws must contain at least the keywords that f requires and must contain a subset of the keywords that f accepts, and allowed_kws must contain a subset of the keywords that f allows (so it can be #false only if f accepts any keyword).
> Function.arity(fun (x): 0)
2
[]
[]
> Function.arity(fun | (x): 0 | (x, y): 1)
6
[]
[]
> Function.arity(fun (x, ...): 0)
-1
[]
[]
> Function.arity(fun (x, ~y: y = 1): 0)
2
[]
[#'~y]
2
[]
[]
> Function.arity(fun (& args, ~& kws): 0)
-1
[]
#false
> def g = Function.reduce_arity(f, 2, [], [])
> g(1)
0
> f(1, 2)
1
> g(1, 2)
fun: arity mismatch;
the expected number of arguments does not match the given number
expected: 1
given: 2
method | ||||
| ||||
method | ||||
expression | |
| |
expression | |
| |
expression | |
(post_fun_expr compose pre_fun_expr)(arg, ...)
is the same as
post_fun_expr(pre_fun_expr(arg, ...))
With the compose operator or its alias ∘, pre_fun_expr must produce a single result when called. With compose, pre_fun_expr can produce multiple values, and the values are all delivered as arguments to post_fun_expr.
In static mode (see use_static) when using compose or ∘, if post_fun_expr with has a known arity, it must accept a single argument. In all modes and including compose_values, static information about the arity of pre_fun_expr is propagated the the operation’s result, and static information about the call result of post_fun_expr is propagated to the operation’s result.
> def partition_to_list = List compose_values List.partition
[[1, 4], [-2, -3]]