Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 if expression.
  • 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(_)) is outer(|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.