Funcer Definition Expressions

To define a funcer, use a funcer definition expression. It has the form for I def N(P1, P2, ..., Pn) O as B ok, where I is the funcer’s input type, N is its name (must start with a lower-case letter), P1, P2, ..., Pn are n or more parameters (if n=0, leave out the parentheses), O is the funcer’s output type, and B is its body. When called, the funcer will evaluate like B, with parameters bound to the arguments passed at call time.

Here are some examples of defining and then calling funcers without parameters:

ProgramTypeValueError
for Num def plusOne Num as +1 ok 1 plusOneNum2
for Num def plusOne Num as +1 ok 1 plusOne plusOneNum3

Parameters

In addition to the input, funcers can take arguments, which are functions that can be called in the funcer body to use their outputs. Parameters specify what kind of arguments are expected. In the simplest case, a parameter is simply a name and a type. This means that inside the funcer body, the given function can be called with any input, by the given name, and will return a value of the given type.

ProgramTypeValueError
for Any def f(x Num) Num as x ok f(1)Num1
for Num def plus(b Num) Num as +b ok 1 plus(1)Num2
for Any def f(a Num, b Num) Arr<Num, Num> as [a, b] ok f(2, 3)Arr<Num, Num>[2, 3]

Parameters with Parameters

In more complex cases, parameters can restrict arguments to be partially applied funcer calls. In such cases, the syntax for a parameter is for I N(P1, P2, ..., Pn) O. This means that inside the funcer body, for inputs of type I, the given funcer will be available to call by the name N with arguments as specified by the parameters P1, P2, ..., Pn (as before, drop the parentheses if n=0) and will return a value of type O. Here, P1, P2, ..., Pn do not contain names.

ProgramTypeValueError
for Num def apply(for Num f Num) Num as f ok 1 apply(+1)Num2
for Num def apply(for Num f Num) Num as f ok 2 =n apply(+n)Num4
for Num def connectSelf(for Num f(for Any Num) Num) Num as =x f(x) ok 1 connectSelf(+)Num2
for Num def connectSelf(for Num f(for Any Num) Num) Num as =x f(x) ok 3 connectSelf(*)Num9
for Num def connectSelf(for Num f(Num) Num) Num as =x f(x) ok 1 connectSelf(+)Num2

Type Variables

Funcer definition expressions can use type variables. They are written between angle brackets and start with an upper-case letter. When applying a funcer to an input expression, type variables are bound from left to right.

ProgramTypeValueError
for <A> def apply(for <A> f <B>) <B> as f ok 1 apply(+1)Num2
for <A>|Null def myMust <A> as is Null then fatal else id ok ok 123 myMustNum123
for <A>|Null def myMust <A> as is Null then fatal else id ok ok "abc" myMustStr"abc"
for <A>|Null def myMust <A> as is Null then fatal else id ok ok null myMust<A>Value error
Code: UnexpectedValue
Message: Component got an unexpected input value.
Got value: null
for <A>|Null def myMust <A> as is <A> then id else fatal ok ok null myMustNullnull
for <A Obj<a: Num, Any>> def f <A> as id ok {a: 1} fObj<a: Num, Void>{a: 1}

Type Variables with Bounds

Type variables can be given upper bounds, they are then constrained to match subtypes of the given upper bound type.

ProgramTypeValueError
for Any def f(for Any g <A Arr<Any...>>) <A> as g ok f([1, "a"])Arr<Num, Str>[1, "a"]
for Any def f(for Any g <A Arr<Any...>>) <A> as g ok f("a")Type error
Code: ArgHasWrongOutputType
Message: An argument has the wrong output type.
Want type: <A Arr<Any...>>
Got type: Str
Arg #: 1

Overloading, Revisited

Funcers are looked up by input type, name, and number of parameters. So you can, for example, define two funcers with the same name but different input types, and use both:

ProgramTypeValueError
for Num def f Num as +2 ok for Str def f Str as +"2" okNullnull
for Num def f Num as +2 ok for Str def f Str as +"2" ok 2 fNum4
for Num def f Num as +2 ok for Str def f Str as +"2" ok "a" fStr"a2"

Funcers cannot be overloaded with respect to the parameters themselves. Thus, calling a funcer on the wrong input or with the wrong number of arguments results in a NoSuchFuncer error. Calling it with the wrong kinds of arguments, by contrast, leads to an ArgHasWrongOutputType error.

ProgramTypeValueError
for Num def f Num as *2 ok fType error
Code: NoSuchFuncer
Message: no such funcer
Input type: Null
Name: f
# params: 0
for <A Obj<a: Num>> def f <A> as id ok {} fType error
Code: NoSuchFuncer
Message: no such funcer
Input type: Obj<Void>
Name: f
# params: 0
for Num def f Num as *2 ok 2 f(2)Type error
Code: NoSuchFuncer
Message: no such funcer
Input type: Num
Name: f
# params: 1
for Str def f Obj<> as {} ok "abc" reFindAll(f)Type error
Code: ArgHasWrongOutputType
Message: An argument has the wrong output type.
Want type: <A Null|Obj<0: Str, start: Num, Any>>
Got type: Obj<Any>
Arg #: 1

Variable Capturing

Funcers have access to the variables defined before, but not after the definition.

ProgramTypeValueError
1 =a for Any def f Num as a ok fNum1
1 =a for Any def f Num as a ok 2 =a fNum1
1 =a for Any def f Num as 2 =a a ok fNum2

Recursion

TBD