function - pannous/warp GitHub Wiki
Functions can be defined in the following natural ways:
function square (n: number){ n*n }
to square a number: return it * it
square of a number = it * it
square(number n)=n*n
square(n)=n*n
square:=it*it
The parser should differentiate between variables and functions via these heuristics:
- functions have braces left of the assignment
square(n)=n*nor - functions have blocks right of the assignment
square = {it*it}or - functions use
:=for the assignmentsquare:=it*it
The difference to square=it*it is that variables get evaluated instantly and only once, whereas
The function square:=it*it is a dangling closure
Under the hood all are represented equally: as the charged data
square = {it*it}
Together with an entry in the pattern dictionary [:square, number] => &square denoting that the symbol 'square' together with the type number should be resolved to the function square.
Functions are unary prefix operators, that is in an expression
a f b c they get parsed as a (f (b c))
Every function generates a suffix operator: square 2 == 2 squared
The suffix word is the function name plus d or ed (squared, sorted) for any function taking an argument.
An ungrouped mix with arithmetic, 1+2 squared, is ambiguous: the compiler asks whether it means 1+(2 squared) or
(1+2) squared (user, 2026-10-03); unanswered it is an error naming both forms. 2 squared+1 is (2 squared)+1.
Partial application
A _ leaves an argument open and makes a new function:
add(a, b) := a + b
inc := add(1, _)
inc 5 // 6
Without the hole, inc := add 1 is an error that names the fix: "add needs 2 arguments, inc := add 1 leaves some
out … fix: inc := add(1, _)". twice := double_it names a
function under a second name, as twice = &double_it does.
Methods
A function taking a class as its first argument is a method of that class. There is no separate method syntax to learn:
x.f(a) is just another way to write f(x, a).
class Deck { cards:int }
sort(d:Deck) := "shuffled " + d.cards + " cards"
sort(Deck(52)) # shuffled 52 cards
Deck(3).sort() # the same call
As f x is f(x), a method call needs no parentheses either: xs.insert 4 is xs.insert(4).
A definition for a class adds a method to the function, it never replaces it: sort still sorts lists.
[3, 1, 2].sort() # [1 2 3]
A method written inside the class body is the same thing, with self and the fields at hand:
class Deck { cards:int; take(k) := cards - k }
Deck(5).take(2) == take(Deck(5), 2)
The most specific method wins: a class's own method beats its parent's, a typed one beats an untyped definition.
class Animal { name }
class Dog extends Animal {}
speak(a:Animal) := a.name + " makes a sound"
speak(d:Dog) := d.name + " barks"
for pet in [Animal("Kitty"), Dog("Rex")] { print speak(pet) } # decided at run time
Every typed argument takes part, not only the first (multiple dispatch, as in Julia):
class Ship{}; class Rock{}
collide(a:Ship, b:Rock) := "crash"
collide(a:Ship, b:Ship) := "bump"
collide(Ship(), Rock()) # crash
When no method fits, or two fit equally well, the error says which methods there are and what to define:
area(circle(1)) # area has no method for circle; methods: area(square), area(rect); fix: define area(s:circle) := …
meet(Dog(), Dog()) # meet(Dog, Dog) is ambiguous: meet(Dog, Animal) and meet(Animal, Dog) fit equally well
A method named like a field keeps both: b.size reads the field, b.size() and size(b) call the method, and the
definition warns about the double meaning.
The standard functions of lists, maps and texts (unique, take, merge, words…) need no use, called either way.
See polymorphism and samples/types/dispatch.warp.