4.3 Function Annotations
annotation | ||||||||
| ||||||||
annotation | ||||||||
| ||||||||
|
The Function.of_arity variant requires that each expr produces a nonnegative integer, and then function must accept that many by-position arguments. The function must require only keywords that are provided as keywords, and it must accept all keywords that are listed. Each keyword must be distinct.
See also -> and Function.all_of.
Due to the current limitation in the function arity protocol, a function must require an exact set of keywords across all arities, even though Rhombus multi-case funs allow non-uniform keyword arguments in different cases. In a Rhombus multi-case fun, the required set of keywords is the “intersection” of required keywords in all cases.
#true
#true
> math.atan is_a Function.of_arity(1, 2)
#true
> (fun (x, ~y): #void) is_a Function.of_arity(1, ~y)
#true
> (fun (x, ~y): #void) is_a Function.of_arity(1)
#false
> (fun (x, ~y = 0): #void) is_a Function.of_arity(1)
#true
> (fun (x, ~y = 0): #void) is_a Function.of_arity(1, ~y, ~z)
#false
annotation | ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
| ||||||||||||||||||||
|
> f(1)
1
> f("hello")
function: argument does not satisfy annotation
argument: "hello"
annotation: Real
> f(1.5)
function: result does not satisfy annotation
result: 1.5
annotation: Int
def: value does not satisfy annotation
value: #<function:fun>
annotation: (Int, Int) -> Int
An arg that starts with a keyword represents a keyword argument. An arg that ends = _ is an optional argument; the default value is not specified, and it is left up to the called function; along the same lines, the annot before = _ is not applied to the argument default.
fun (x, y, ~mode: mode):
> g(1, 2, ~mode: #'minus)
-3
> g(1, 2, ~mode: 3)
function: argument does not satisfy annotation
argument: 3
annotation: Symbol
> g2(1, 2)
3
When an arg has an id and ::, the argument is named for use in later argument annotations, including result annotations. As in fun, each name refers to the argument after any conversion implied by its annotation.
> def both :: (x :: String, satisfying(fun (y): x < y)) -> List:
fun (x, y): [x, y]
> both("apple", "banana")
["apple", "banana"]
> both("apple", "aardvark")
function: argument does not satisfy annotation
argument: "aardvark"
annotation: satisfying(fun (y): x < y)
An arg written with & or ~& stands for any number of by-position and by-keyword arguments, respectively. By-position arguments for & are gathered into a list, and by-keyword arguments for ~& are gathered into a map whose keys are keywords. Alternatively, extra by-position arguments can be covered by an annot followed by .... Arguments gathers with & or ~& can be named for later reference.
#<function:...rrow-annotation.rkt:313:14>
::: value does not satisfy annotation
value: #<function:fun>
annotation: (Int, Int, ...) -> Int
kws.keys()
> k(~a: 1, ~b: 2, ~c: 3)
[#'~a, #'~b, #'~c]
If args is (~any), then no constraint is placed on the function arguments. Note that (~any) is different than (Any, ...) or (& Any), which require that the function accept any number of arguments. The arity of the converted function is the same as the original function.
> m(1)
2
> m(1, 2)
3
> m(1.0)
function: result does not satisfy annotation
result: 2.0
annotation: Int
> m is_a Function.of_arity(3)
#false
Result annotations are analogous to argument annotations, except that keywords cannot be used for results. A multiple-annotation result sequence in parentheses can be preceded optionally with values. Note that using Any as the result annotation implies a check that the converted function produces a single result when it is called. In the special case that the result annotation sequence is ~any, (~any), or equivalent to (Any, ...) or (& Any), then a call to the original function converted by the -> annotation is a tail call with respect to the converting wrapper.
fun (n):
> n_values(1)
0
> n_values(3)
0
1
2
To accept multiple argument annotations in parentheses, -> relies on help from #%parens. Relying on #%parens for non-annotation to the left of -> is why ~any for an argument must be in parentheses.
See also Function.all_of, which can be used not only to join multiple ->, but to provide a name that the converted function uses for reporting failed annotation checks.
annotation | ||||||||||
| ||||||||||
| ||||||||||
|
The difference between using Function.all_of to combine the arrow_annots and using && is that && would effectively apply every arrow_annot to every call of the function, while Function.all_of selects only the first arrow_annot whose argument annotations are satisfied by the supplied arguments for each call to the function.
If a ~name form is among the annot_or_names, it can appear only once. The body forms under ~name are evaluated only when an error is to be reported, and the result must satisfy error.Who. The ~name clause is removed from the textual representation of Function.all_of when reporting a failure to match the overall annotation. The textual representation of Function.all_of is further reduced to arrow_annot when only one is provided (with or without ~name).
> def f :: Function.all_of(() -> Int,
fun | (): saved
| (v): saved := v
> f(1)
> f()
1
> saved := #false
> f()
function: result does not satisfy annotation
result: #false
annotation: Int