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

Declaring Functions and Values

Grammar

def_bind      ::= "def" def_name [generics] [def_signature] "=" exp
def_name      ::= name | "(" symbol ")"
def_signature ::= "(" param ("," param)* ")" type   -- function form
                | ":" type                          -- typed constant
param         ::= name ":" type
generics      ::= "<" generic_param ("," generic_param)* ">"
generic_param ::= "[" name "]" | UpperName

entry_bind    ::= "entry" name "(" [entry_param ("," entry_param)*] ")"
                  type "=" exp
entry_param   ::= name ":" type

UpperName is an identifier whose first character is uppercase.

Description

A def declaration binds a value or function to a name at module scope. The body is an arbitrary expression that may use only names already in scope at the point of binding; forward references are not permitted, and functions may not be recursive.

A def may take parameters and a return type, in which case it defines a function; without parameters it is a constant.

def gravity: f32 = 9.81
def step(dt: f32, v: f32) f32 = v + gravity * dt

Type Inference

Hindley-Milner-style type inference fills in argument and return types when context allows. Explicit annotations are required only when inference cannot determine a unique type. Sizes participate in the type system; see Size Types for the rules.

def step(dt, v) = v + 9.81 * dt

Here dt, v, and the return type are all inferred as f32: 9.81 defaults to f32, the multiplication constrains dt, and the addition constrains v.

Polymorphic Functions

A function may be polymorphic over types and sizes through the <...> generics list. Size parameters are written [n]; type parameters are uppercase identifiers:

def reverse<[n], A>(xs: [n]A) [n]A = ???

Generics need not cover the type of every parameter. Any argument whose type isn’t tied to a declared generic is given a fresh type variable by inference:

def pair<A>(x: A, y) = (x, y)

A fresh type variable is invented for y.

Type Parameter Resolution

Type and size parameters are inferred from arguments at call sites — they are not passed explicitly. If the same type parameter A appears in multiple parameter positions, all arguments bound to it must agree in both shape and element type. For example:

def pair<A>(x: A, y: A) = (x, y)

pair([1], [2, 3]) is ill-typed because the two arguments bind A to arrays of different sizes.

Aliasing Restrictions

To simplify the handling of in-place updates (see In-place Updates), the value returned by a function may not alias any global variables.

Entry Points

An entry declaration defines a complete operation exposed to the host. It is not a function value and cannot be called by another Wyn declaration. Array operators express ordinary data-parallel computation; the entry body is otherwise an ordinary expression.

A compilation has one root source module. Every entry declared directly in that module is exposed to the host and seeds whole-program reachability. An imported source module may not declare an entry; reusable operations in a library are declared with def, and the root wraps them in its own entry declarations. The compiler emits all root entries together, so selecting the root module also selects the complete host interface.

Entry declarations differ from def declarations in three ways:

  • Parameters require the name: type form; pattern destructuring is not allowed.
  • Every parameter and result is part of the external interface of the operation. Intermediate values are not entry parameters or results.
  • An empty parameter list still requires the parentheses: entry foo() ret = ....

The name of an entry must not contain an apostrophe ('), even though apostrophes are otherwise permitted in identifiers.