On this page:
Function
Function.of_  arity
->
Function.all_  of
8.15.0.12

4.3 Function Annotations🔗ℹ

The Function annotation matches any function.

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.

> math.cos is_a Function

#true

> math.cos is_a Function.of_arity(1)

#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

args -> results

 

args

 = 

annot

 | 

(arg, ..., rest_arg, ...)

 | 

(~any)

 

results

 = 

annot

 | 

(result, ..., rest_result, ...)

 | 

values(result, ..., rest_result, ...)

 | 

~any

 | 

(~any)

 

arg

 = 

plain_arg

 | 

named_arg

 | 

rest_arg

 

plain_arg

 = 

annot

 | 

annot = _

 | 

keyword: annot

 | 

keyword: annot = _

 

named_arg

 = 

id :: annot

 | 

id :: annot = _

 | 

keyword: id :: annot

 | 

keyword: id :: annot = _

 

rest_arg

 = 

annot , ellipsis

 | 

& list_annot

 | 

~& map_annot

 | 

& id :: list_annot

 | 

~& id :: map_annot

 

result

 = 

annot

 | 

id :: annot

 

rest_result

 = 

annot , ellipsis

 | 

& list_annot

 | 

& id :: list_annot

 

ellipsis

 = 

...

A converter annotation that is immediately satisfied by a function that has a compatible argument count and keyword arguments. When a function converted by the annotation is called, then the argument annotations are applied to the actual arguments, and the result annotations are applied to the results. An error is reported if the number of actual results does not match the number of result annotations.

> def f :: Real -> Int = (fun (x): x)

> 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 f2 :: (Int, Int) -> Int = (fun (x): x)

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.

> def g :: (Int, Int, ~mode: Symbol) -> Int:

    fun (x, y, ~mode: mode):

      (x + y) * (if mode == #'minus | -1 | 1)

> g(1, 2, ~mode: #'minus)

-3

> g(1, 2, ~mode: 3)

function: argument does not satisfy annotation

  argument: 3

  annotation: Symbol

> def g2 :: (Int, Int, ~mode: Symbol = _) -> Int:

    fun (x, y, ~mode: mode = "plus"):

      (x + y) * (if mode == #'minus | -1 | 1)

> 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.

> (fun (x, y, z, ...): 0) :: (Int, Int) -> Int

#<function:...rrow-annotation.rkt:313:14>

> (fun (x, y, z, ...): 0) :: (Int, Int, ...) -> Int

::: value does not satisfy annotation

  value: #<function:fun>

  annotation: (Int, Int, ...) -> Int

> def k :: (~& kws :: Map) -> satisfying(fun (r):

                                           r.length() == kws.length()):

    fun (~& kws):

      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.

> def m :: (~any) -> Int:

    fun (x, y = x): x + y

> 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.

> def n_values :: (Int) -> (Any, ...):

    fun (n):

      values(& 0..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

Function.all_of(annot_or_name, ...)

 

annot_or_name

 = 

arrow_annot

 | 

~name:

  body

  ...

Creates an annotation that is satisfied by a function that satisfies every arrow_annot, each of which should correspond to a -> annotation (or one that is ultimately defined by expansion to ->).

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 mutable saved = 0

> def f :: Function.all_of(() -> Int,

                           Int -> Void):

    fun | (): saved

        | (v): saved := v

> f(1)

> f()

1

> saved := #false

> f()

function: result does not satisfy annotation

  result: #false

  annotation: Int

> def f :: Function.all_of(Int -> Int,

                           String -> String):

    fun (x): x

> f(1)

1

> f("apple")

"apple"

> f(#'apple)

function: no matching case for arguments

> fun build(filter :: Function.all_of(Int -> Int,

                                      String -> String,

                                      ~name: "filter for build")):

    [filter(1), filter("apple")]

> build(fun (x): "apple")

filter for build: result does not satisfy annotation

  result: "apple"

  annotation: Int