Higher-order Functions
Within a Wyn program, functions can be named, passed as arguments, and returned from other functions. Function values do not exist at runtime, however (see Program Structure), so the following restrictions apply to functions and to any record or tuple containing a function (a functional type):
- Arrays of functions are not permitted.
- A function cannot be returned from an
ifexpression. - A loop parameter cannot be a function.
See also In-place Updates for details on how consumption interacts with higher-order functions.
Function Types
Functions are classified by function types written A -> B. Arrow types are
right-associative, so A -> B -> C means A -> (B -> C).
Function Arity and Partial Application
Wyn functions are not curried by default. Every function has a fixed arity (number of arguments), and every function application must supply exactly that many arguments. Partial application is not allowed. Call sections do not relax this rule: they construct explicit lambdas whose bodies contain fully saturated applications.
def add(x: i32, y: i32) i32 = x + y
-- Valid: fully applied
def result = add(1, 2)
-- INVALID: partial application
def add_one = add(1) -- Error: function requires 2 arguments
This restriction applies uniformly to:
- Top-level function definitions
- Anonymous functions
- Built-in functions
- Functions passed as higher-order arguments
Call Sections
When a function value with some argument positions left open is needed, use placeholder arguments:
def add(x: i32, y: i32, z: i32) i32 = x + y + z
-- Create a 2-arity function that calls `add` with the middle arg
-- fixed.
def add_with_5 = add(_, 5, _) -- Produces (i32, i32) -> i32
-- Create a 1-arity function with two of the three args fixed.
def add_one = add(_, 1, 0) -- Produces i32 -> i32
-- Usage
def result = add_one(5) -- Returns 6
A call containing _ placeholders is a call section:
add(_, 5, _)
This desugars to |x, z| add(x, 5, z). No partial application is involved.
- Each
_marks a placeholder position that becomes a distinct parameter of the new function. - Non-placeholder arguments remain ordinary expressions in the generated lambda body.
- The resulting function has arity equal to the number of
_placeholders. - The resulting function is itself non-curried (it requires all placeholders to be filled at once).
- A placeholder belongs to its nearest containing call. Thus
outer(inner(_))isouter(|x| inner(x)), not|x| outer(inner(x)).
A section in which every argument is a placeholder is eta-equivalent to the
bare function value. Thus f(_) and f are interchangeable when f has
arity one, and f(_, _) and f are interchangeable when f has arity two.
The bare form is normally clearer.