On this page:
fun
fun
fun
fun
Function.map
Function.for_  each
Function.pass
Function.arity
Function.reduce_  arity
Function.name
Function.rename
compose
compose_  values
8.15.0.12

4.1 Functions🔗ℹ

A function is normally written with the fun definition form or fun expression form, but see also Callable.

definition

fun id_name(bind, ...):

  body

  ...

 

definition

fun id_name case_maybe_kw_opt:

  option; ...

  body

  ...

 

definition

fun

| id_name case_maybe_kw:

    case_option; ...

    body

    ...

| ...

 

definition

fun id_name maybe_res_annot

| id_name case_maybe_kw:

    case_option; ...

    body

    ...

| ...

 

definition

fun id_name maybe_res_annot:

  option; ...

| id_name case_maybe_kw:

    case_option; ...

    body

    ...

| ...

 

case_maybe_kw_opt

 = 

(bind_maybe_kw_opt, ..., rest, ...) maybe_res_annot

 

case_maybe_kw

 = 

(bind_maybe_kw, ..., rest, ...) maybe_res_annot

 

option

 = 

~doc

 | 

~doc:

  desc_body

  ...

 | 

name_option

 | 

case_option

 

name_option

 = 

~name op_or_id_name

 | 

~name: op_or_id_name

 | 

who_option

 

case_option

 = 

~unsafe:

  unsafe_body

  ...

 | 

who_option

 

who_option

 = 

~who id

 | 

~who: id

 

bind_maybe_kw_opt

 = 

bind

 | 

keyword: bind

 | 

bind = default_expr

 | 

bind: default_body; ...

 | 

keyword: bind = default_expr

 | 

keyword: bind: default_body; ...

 | 

keyword

 | 

keyword = default_expr

 

bind_maybe_kw

 = 

bind

 | 

keyword: bind

 | 

keyword

 

maybe_res_annot

 = 

:: annot

 | 

:~ annot

 | 

:: values(annot, ...)

 | 

:~ values(annot, ...)

 | 

:: (annot, ...)

 | 

:~ (annot, ...)

 | 

ϵ

 

rest

 = 

repet_bind , ellipsis

 | 

& list_bind

 | 

~& map_bind

 

ellipsis

 = 

...

Binds id_name as a function in the expr space. Note that when id_name is not supplied, fun is an expression form instead of a definition form.

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.

fun f(x):

  x+1

> f(0)

1

fun List.number_of_items(l):

  List.length(l)

> List.number_of_items(["a", "b", "c"])

3

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.

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

  [x, y]

> f(0)

[0, 1]

> f(0, 2)

[0, 2]

fun transform([x, y],

              ~scale: factor = 1,

              ~dx: dx = 0,

              ~dy: dy = 0):

  [factor*x + dx, factor*y + dy]

> transform([1, 2])

[1, 2]

> transform([1, 2], ~dx: 7)

[8, 2]

> transform([1, 2], ~dx: 7, ~scale: 2)

[9, 4]

fun invalid(x = 1, y):

  x+y

fun: default-value expression missing

fun

| valid(y): valid(1, y)

| valid(x, y): x+y

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

fun

| hello(name):

    "Hello, " +& name

| hello(first, last):

    hello(first +& " " +& last)

> hello("World")

"Hello, World"

> hello("Inigo", "Montoya")

"Hello, Inigo Montoya"

fun

| is_passing(n :: Number): n >= 70

| is_passing(pf :: Boolean): pf

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

fun

| is_sorted([] || [_]):

    #true

| is_sorted([head, next, & tail]):

    head <= next && is_sorted([next, & tail])

> is_sorted([1, 2, 3, 3, 5])

#true

> is_sorted([1, 2, 9, 3, 5])

#false

fun

| is_sorted([] || [_]):

    #true

| is_sorted([head, next, tail, ...]):

    head <= next && is_sorted([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.

fun hello :: String

| hello(name):

    "Hello, " +& name

| hello():

    #false

> hello("World")

"Hello, World"

> hello()

hello: result does not satisfy annotation

  result: #false

  annotation: String

fun things_to_say :: (String, String)

| things_to_say():

    values("Hi", "Bye")

| things_to_say(more):

    values("Hi", "Bye", more)

> 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—that is, to the symbol form of the defined id_name or the op_or_id_name provided with ~name. A ~who binding is particularly useful as a symbol that can be provided to error. The id for ~who is bound after the declaration, which means that it is either outside multiple cases (and potentially shadowed by arguments in those cases) or inside of a single case (where it potentially shadows argument bindings).

fun trivial(x :: Int):

  ~name: easy

  ~who: who

  if x == 0

  | error(~who: who, "zero")

  | x

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

import rhombus/fixnum

fun fixnum_sum(ns :: List.of(Fixnum)) :: Fixnum:

  ~unsafe:

    // assume all fixnums and that result fits:

    for values(res = 0) (n: ns):

      res fixnum.(+) n

  // use generic arithmetic, check fixnum result at end:

  for values(res = 0) (n: ns):

      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

fun (bind, ...):

  name_option; ...

  body

  ...

 

expression

fun case_maybe_kw_opt:

  name_option; ...

  body

  ...

 

expression

fun maybe_res_annot

| case_maybe_kw:

    who_option; ...

    body

    ...

| ...

 

expression

fun maybe_res_annot:

  name_option; ...

| case_maybe_kw:

    who_option; ...

    body

    ...

| ...

Produces a function value. Note that when an id_name appears after fun or after a |, fun is a definition form instead of an expression form.

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.

def identity = fun (x): x

> identity(1)

1

fun curried_add(x):  // definition

  fun (y):           // expression

    x + y

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

entry point

fun args_and_body

 

immediate callee

fun args_and_body

The entry point and immediate callee forms of fun have the same syntax and behavior as the expression form of fun.

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 (f :: Function).map(args0 :: List, args :: List, ...)

  :: List

 

method

method (f :: Function).for_each(args0 :: List, args :: List, ...)

  :: Void

Applies f to each element of each args (including args0), iterating through the args lists together, so f must take as many arguments as the number of given args lists. For Function.map, the result is a list containing the result of each call to f in order. For Function.for_each, the result is #void, and the result of each call to f is ignored.

> Function.map((_ + _), [1, 2, 3], [4, 5, 6])

[5, 7, 9]

> (_ + _).map([1, 2, 3], [4, 5, 6])

[5, 7, 9]

> println.for_each([1, 2, 3])

1

2

3

function

fun Function.pass(& _, ~& _) :: Void

Accepts any arguments and returns #void.

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()

> Function.pass(

    "a",

    ~weird: "call",

    & ["these", "are", "ignored"],

    ~& {#'~and: "thrown"},

    "away",

  )

> block:

    let [[x, ...], ...] = [[1, 2, 3], [4, 5]]

    // a good alternative to `for`

    Function.pass(println(x), ..., ...)

1

2

3

4

5

method

method (f :: Function).arity()

  :: values(Int, List.of(Keyword), maybe(List.of(Keyword)))

 

method

method (f :: Function).reduce_arity(

  by_pos_mask :: Int,

  required_kws :: List.of(Keyword),

  allowed_kws :: maybe(List.of(Keyword)),

  ~name: name :: Symbol = Function.name(f),

  ~realm: realm :: Symbol = #'rhombus

) :: Function

The Function.arity method reports information about the arguments that a function accepts:

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]

> Function.arity(Function.arity)

2

[]

[]

> Function.arity(fun (& args, ~& kws): 0)

-1

[]

#false

> def f = fun | (x): 0 | (x, y): 1

> 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 (f :: Function).name() :: Symbol

 

method

method (f :: Function).rename(

  name :: Symbol,

  ~realm: realm :: Symbol = #'rhombus

) :: Function

The Function.name method reports a function’s name, which is used for printing the function. The Function.rename method returns a function that is like f, but with the given name. A function’s realm may affect how the function name is printed in different contexts.

Produces a function that that composes the result functions of pre_fun_expr and post_fun_expr so that

(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 nstr = to_string compose math.abs

> nstr(-100)

"100"

> def partition_to_list = List compose_values List.partition

> partition_to_list([1, -2, -3, 4], (_ > 0))

[[1, 4], [-2, -3]]