1.4. Functions

Like most other programming languages, Orc provides the capability to define functions, which are expressions that have a defined name, and have some number of parameters. Functions are declared using the keyword def, in the following way:

def add(x,y) = x+y

The expression to the right of the = is called the body of the function. x and y are the parameters. By convention, all function names begin with a lowercase letter.

After defining the function, we can call it. A function call looks just like a site call. To execute a call, we treat it like a sequence of val declarations associating the parameters with the arguments, followed by the body of the function. Every value published by the body expression is published by the call. This is unlike a site call, which publishes at most once.

{- add(1+2, 3+4) is equivalent to: -}

val x = 1+2
val y = 3+4
x+y

{-
OUTPUT:
10
-}

Examples

Notice that the execution of a function call can proceed even if some of the arguments haven't published a value yet. The parts of the body that depend on them will simply block.

def demo(x,y) = x | y | x+y
demo(3, Rwait(2000) >> 4)

This call publishes 3 immediately, but blocks for 2 seconds before publishing 4 and 7.

A function definition or call may have zero arguments, in which case we write () for the arguments.

def Zero() = 0

1.4.1. Recursion

A function can be recursive; that is, the name of a function may be used in its own body.

def sumto(n) = if n < 1 then 0 else n + sumto(n-1)

The call sumto(5) publishes 15.

A recursive function may run forever, publishing an infinite number of times. The function metronome is a classic example; a call to metronome publishes a signal once per second, forever:

def metronome() = signal | Rwait(1000) >> metronome()

Mutual recursion is also supported.

def even(n) = 
  if (n :> 0) then odd(n-1)
  else if (n <: 0) then odd(n+1)
  else true
def odd(n) = 
  if (n :> 0) then even(n-1)
  else if (n <: 0) then even(n+1)
  else false

There is no special keyword for mutual recursion; any contiguous sequence of function declarations is assumed to be mutually recursive. Also, note that :> and <: are the Orc symbols for 'greater than' and 'less than' respectively.

1.4.2. Clauses

The combination of functions and pattern matching offers a powerful capability: clausal definition of functions. We can define expressions which execute different code depending on the structure of their arguments.

Here's an example.

def sum([]) = 0
def sum(h:t) = h + sum(t)

sum(l) publishes the sum of the numbers in the list l. It has two clauses: one which matches the empty list, and one which matches any nonempty list. If its argument is an empty list, it returns 0, the appropriate sum for an empty list. If the argument is a nonempty list, it adds the first element of that list to the sum of all of the other elements. In this way, it recursively finds the sum of the list.

A function may have multiple clauses, each of which has a sequence of patterns to match each argument, and a body expression. Naturally, all clauses of a function must have the same number of arguments. Any contiguous sequence of definitions with the same name and different arguments is interpreted as a clausal definition, where each individual declaration is a clause of the larger function.

When the function is called, the clauses are tried in the order in which they appear until a match is found. If no clause matches, the call remains silent.

We can use a literal pattern to define the base case of a recursive function in a straightforward way.

{- Fibonacci numbers -}
def fib(0) = 1
def fib(1) = 1
def fib(n) = fib(n-1) + fib(n-2)

This definition of the Fibonacci function is straightforward, but slow, due to the repeated work in recursive calls to fib. We can define a linear-time version, again with the help of pattern matching:

{- Alternate definition of the Fibonacci function -}

{- A helper function: find the pair (Fibonacci(n), Fibonacci(n+1)) -}
def H(0) = (1,1)
def H(n) = H(n-1) >(x,y)> (y, x+y)

def fib(n) = H(n) >(x,_)> x

As a more complex example of matching, consider the following function which takes a list argument and returns a new list containing only the first n elements of the argument list.

def take(0,_) = []
def take(n,h:t) = h:take(n-1, t)

1.4.3. Guards

Each clause of a function definition may also have a guard: a Boolean expression which determines whether or not the clause applies. If the guard publishes false, then the next clause is tried, just as if some pattern had failed to match.

We can add guards to a previous example to protect against the degenerate case of a negative argument:

{- Fibonacci numbers -}
def fib(0) = 1
def fib(1) = 1
def fib(n) if (n :> 1) = fib(n-1) + fib(n-2)

We can also improve the readability of a previous example:

def even(n) if (n :> 0) = odd(n-1)
def even(n) if (n <: 0) = odd(n+1)
def even(0) = true 
def odd(n) if (n :> 0) = even(n-1)
def odd(n) if (n <: 0) = even(n+1)
def odd(0) = false