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:
| Program | Type | Value | Error |
|---|---|---|---|
for Num def plusOne Num as +1 ok 1 plusOne | Num | 2 | |
for Num def plusOne Num as +1 ok 1 plusOne plusOne | Num | 3 |
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.
| Program | Type | Value | Error |
|---|---|---|---|
for Any def f(x Num) Num as x ok f(1) | Num | 1 | |
for Num def plus(b Num) Num as +b ok 1 plus(1) | Num | 2 | |
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.
| Program | Type | Value | Error |
|---|---|---|---|
for Num def apply(for Num f Num) Num as f ok 1 apply(+1) | Num | 2 | |
for Num def apply(for Num f Num) Num as f ok 2 =n apply(+n) | Num | 4 | |
for Num def connectSelf(for Num f(for Any Num) Num) Num as =x f(x) ok 1 connectSelf(+) | Num | 2 | |
for Num def connectSelf(for Num f(for Any Num) Num) Num as =x f(x) ok 3 connectSelf(*) | Num | 9 | |
for Num def connectSelf(for Num f(Num) Num) Num as =x f(x) ok 1 connectSelf(+) | Num | 2 |
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.
| Program | Type | Value | Error |
|---|---|---|---|
for <A> def apply(for <A> f <B>) <B> as f ok 1 apply(+1) | Num | 2 | |
for <A>|Null def myMust <A> as is Null then fatal else id ok ok 123 myMust | Num | 123 | |
for <A>|Null def myMust <A> as is Null then fatal else id ok ok "abc" myMust | Str | "abc" | |
for <A>|Null def myMust <A> as is Null then fatal else id ok ok null myMust | <A> | Value error | |
for <A>|Null def myMust <A> as is <A> then id else fatal ok ok null myMust | Null | null | |
for <A Obj<a: Num, Any>> def f <A> as id ok {a: 1} f | Obj<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.
| Program | Type | Value | Error |
|---|---|---|---|
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 |
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:
| Program | Type | Value | Error |
|---|---|---|---|
for Num def f Num as +2 ok for Str def f Str as +"2" ok | Null | null | |
for Num def f Num as +2 ok for Str def f Str as +"2" ok 2 f | Num | 4 | |
for Num def f Num as +2 ok for Str def f Str as +"2" ok "a" f | Str | "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.
| Program | Type | Value | Error |
|---|---|---|---|
for Num def f Num as *2 ok f | Type error | ||
for <A Obj<a: Num>> def f <A> as id ok {} f | Type error | ||
for Num def f Num as *2 ok 2 f(2) | Type error | ||
for Str def f Obj<> as {} ok "abc" reFindAll(f) | Type error |
Variable Capturing
Funcers have access to the variables defined before, but not after the definition.
| Program | Type | Value | Error |
|---|---|---|---|
1 =a for Any def f Num as a ok f | Num | 1 | |
1 =a for Any def f Num as a ok 2 =a f | Num | 1 | |
1 =a for Any def f Num as 2 =a a ok f | Num | 2 |
Recursion
TBD