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: typeform; 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.