The def
declaration defines a function.
A function definition consists of an identifier, a sequence of parameters,
and a body expression.
An Orc function behaves much like a function, method, procedure, or subroutine in other programming languages. However, there are two key differences:
Orc functions have additional features, many of them adopted from typed functional programming languages:
Orc functions may be recursive. A group of functions may be mutually recursive.
Patterns may be used as function parameters.
A function may have a guard, which allows the function body to be executed only if a given condition holds.
A function may be defined by multiple clauses.
|
The simplest case of argument binding uses only variables and wildcards as parameters.
When a function is called, the function body executes, and in parallel the argument expressions of the call are deflated.
If the execution of the body encounters a use of a parameter whose corresponding argument expression has not yet published a value, that use blocks until the argument value is available, but the rest of the body continues to execute.
Whenever the execution of the body would publish a value, the function call publishes that value. If execution of the body halts, the function call also halts. As a result, a function call might publish any number of times, including zero.
If the function call is killed, execution of the body expression is also immediately killed.
Because functions are lenient, the following two programs are equivalent:
def fn(x,y) = E fn(G,H)
val x = G val y = H E
A function parameter may be any pattern. A lenient pattern is either a variable pattern or a wildcard pattern; such patterns will never fail to match. Any other pattern is a strict pattern, which could fail to match.
When a function is called, the call blocks until a value is available for each strict pattern. The values are then matched against the strict patterns. If all of these matches succeed, then the function call executes as described earlier. If any strict pattern match fails, or if any of the argument expressions corresponding to a strict pattern halts, then the function call halts.
Suppose P
is a strict pattern. The following two programs are equivalent:
def fn(x,P) = E fn(G,H)
val x = G val z = H z >P> E
Functions can be recursive; that is, the name of a function may be used in its own body.
{- A recursive factorial function -} def fact(n) = if (n <= 1) then 1 else n * fact(n-1)
A recursive function might continue executing indefinitely, producing an infinite number of publications.
{- Publishes a signal every second, forever -} def metronome() = signal | Rwait(1000) >> metronome()
A set of functions may be mutually recursive by naming each other in their bodies. There is no special keyword for mutual recursion; whenever two or more function definitions are adjacent, they are allowed to mutually recurse.
A function definition may include a guard, of the form if
(
E
)
.
When a function is called, and each strict pattern matches successfuly as described earlier, then the guard expression
E
is deflated. If E
deflates to true
, then the function
body is executed. If E
deflates to some other value, or halts without publishing a value,
then the function call halts silently.
Suppose P
is a strict pattern and Gd
is a guard expression.
The following two programs are equivalent:
def fn(x,P) if (Gd) = E fn(G,H)
val x = G val z = H z >P> Ift(Gd) >> E
A function can be defined by a sequence of clauses: repeated function definitions with the same identifier but different parameters. Each clause must have the same number of parameters.
When a function with multiple clauses is called, the argument expressions are deflated, and in parallel the first clause is executed. The clause will fail under any of the following conditions:
One of the parameters is a strict pattern, and that pattern fails to match.
One of the parameters is a strict pattern, and the corresponding argument expression has halted silently.
There is a guard expression, and it did not deflate to true
.
If each strict pattern matches successfully, and the guard expression (if present) deflates to true
,
then the corresponding function body executes.
If the clause fails, then the next clause is executed.
If the last clause fails, then the function call halts silently.
When a function is defined, the function identifier is bound to a closure. A definition must be given additional type information so that the typechecker can deduce the correct function type for the identifier.
{- Publish pairs of results from three computations, as they become available. -} def pairs(x, y, z) = (x, y) | (x, z) | (y, z) pairs(Rwait(2000) >> 0, 1, Rwait(1000) >> 2) {- OUTPUT: (1, 2) (0, 1) (0, 2) -} {- OUTPUT: (1, 2) (0, 2) (0, 1) -}
{- Define a parallel-or function. -} def parallelor(x,y) = val first = Ift(x) >> true | Ift(y) >> true | (x || y) first parallelor(false, Rwait(1000) >> true) {- OUTPUT: true -}
{- Test if a number is even or odd, using mutual recursion -} def even(n) = Ift(n = 0) >> true | Ift(n <: 0) >> odd(n+1) | Ift(n :> 0) >> odd(n-1) def odd(n) = Ift(n = 0) >> false | Ift(n <: 0) >> even(n+1) | Ift(n :> 0) >> even(n-1) odd(-4) {- OUTPUT: false -}
{- Publish the head of a nonempty list. If the list is empty, halt silently. -} def head(h:_) = h head([2, 3]) | head([]) {- OUTPUT: 2 -}
{- Find the length of a list -} def length([]) = 0 def length(_:rest) = length(rest) + 1 length([1, 2, 4]) {- OUTPUT: 3 -}
{- Sum the elements of a list -} def sum([]) = 0 def sum(h:t) = h + sum(t) sum([1, 2, 3]) {- OUTPUT: 6 -}
{- "Zip" a pair of lists together into a list of pairs. If the lists are of inequal length, halt silently. -} def zip(x:xs, y:ys) = (x, y):zip(xs, ys) def zip([], []) = [] zip([0, 1], [false, true]) | zip([1, 2, 3], signal) {- OUTPUT: [(0, false), (1, true)] -}
{- Fibonacci numbers -} def fib(0) = 1 def fib(1) = 1 def fib(n) if (n :> 1) = fib(n-1) + fib(n-2) fib(5) {- OUTPUT: 8 -}
Related Reference Topics
Related Tutorial Sections