In this section we introduce Cor, a pure functional subset of the Orc language. Users of functional programming languages such as Haskell and ML will already be familiar with many of the key concepts of Cor.
A Cor program is an expression. Cor expressions are built up recursively from smaller expressions. Cor evaluates an expression to reduce it to some simple value which cannot be evaluated further, for example a list of numbers or a Boolean truth value. This value is called the result of the expression.
In the following subsections we introduce the concepts of Cor. First, we talk about
simple constants, such as numbers and truth values, and the operations that we can perform
on those values. Then we introduce conditionals (if then else). Then we
introduce variables and binding, as a way to give a name to the value of an expression.
After that, we talk about constructing data structures, and examining those structures
using patterns. Lastly, we introduce functions.
Table 1.1 describes the syntax of Cor. Each part of the syntax is explained in a subsequent section.
Table 1.1. Syntax of the Functional Subset of Orc (Cor)
| E | ::= | Expression | ||
| C | constant value | |||
| | | X | variable | ||
| | |
( E , ... , E )
| tuple | ||
| | |
[ E , ... , E ]
| list | ||
| | | X( E , ... , E )
| function call | ||
| | | E op E | operator | ||
| | |
if E then E else E | conditional | ||
| | |
lambda ( P , ... , P ) = E | closure | ||
| | | D E | scoped declaration | ||
| C | ::= | Boolean | Number | String | Constant | |
| X | ::= | Identifier | Identifier | |
| D | ::= | Declaration | ||
val P = E | value declaration | |||
| | |
def X( P , ... , P ) = E | function declaration | ||
| P | ::= | Pattern | ||
| X | variable | |||
| | | C | constant | ||
| | |
_
| wildcard | ||
| | |
( P , ... , P )
| tuple | ||
| | |
[ P , ... , P ]
| list |
Where relevant, syntactic constructs are ordered by precedence, from highest to lowest. For example, among expressions, calls have higher precedence than operators, which in turn have higher precedence than conditionals.
The simplest expression one can write is a constant. It evaluates trivially to that constant value.
Cor has three types of constants, and thus for the moment three types of values:
true and false 5, -1, 2.71828, ... "orc", "ceci n'est pas une |"Cor has a standard set of arithmetic, logical, and comparison operators. As in most other programming languages, they are written in the usual infix style. They have Java-like operator precedence, which can be overridden by adding parentheses.
Examples
1+2 evaluates to 3.(98+2)*17 evaluates to 1700.4 = 20 / 5 evaluates to true.3-5 >= 5-3 evaluates to false.true && (false || true) evaluates to true."leap" + "frog" evaluates to "leapfrog".Here is the full set of operators that Cor supports:
Table 1.2. Operators of Cor
| Arithmetic | Comparison | Logical | String | ||||
|---|---|---|---|---|---|---|---|
+
| addition |
=
| equality |
&&
| logical and |
+
| concatenation |
-
| subtraction |
/=
| inequality |
||
| logical or | ||
*
| multiplication |
<
| less than |
~
| logical not | ||
/
| division |
>
| greater than | ||||
%
| modulus |
<=
| less than or equal | ||||
**
| exponent |
>=
| greater than or equal | ||||
There is also a unary negation operator, written -, for example -(2 ** 5).
The = operator can compare values of any type. Values of different type are always unequal; for example,
10 = true evaluates to false.
Numbers with no decimal part, such as 3, are treated as integers. Arithmetic operators with two integer arguments will perform
an integer operation and return an integer result; for example, 5 / 2 performs integer division and evaluates to 2.
However, if either argument to an operator has a decimal part (even if it is trivial, as in 3.0), the other argument will
be promoted, and a decimal operation will be peformed. For example, 5 / 2.0 and 5.0 / 2 both perform decimal
division and evaluate to 2.5.
There are situations where an expression evaluation is stuck, because it is attempting to perform
some impossible operation and cannot compute a value. In that case, the expression is silent.
An expression is also silent if it depends on the result of a silent subexpression. For example, the following
expressions are silent: 10/0, 6 + false, 3 + 1/0, 4 + true = 5.
Cor is a dynamically typed language. A Cor implementation does not statically check the type correctness of an expression; instead, an expression with a type error is simply silent when it is evaluated.
A conditional expression in Cor is of the form
if E then F else G.
Its meaning is similar to that in other languages: the value
of the expression is the value of F if and only if E evaluates to true,
or the value of G if and only if E evaluates to false. Note that G is not
evaluated at all if E evaluates to true, and similarly F is not evaluated
at all if E evaluates to false. Thus, for example, evaluation of
if true then 2+3 else 1/0 does not evaluate 1/0;
it only evaluates 2+3.
Unlike other languages, expressions in Cor may be silent. If E is silent, then the entire expression is silent. And if E evaluates to true but F is silent (or if E evaluates to false and G is silent) then the expression is silent.
Note that conditionals have lower precedence than any of the operators.
For example, if false then 1 else 2 + 3 is equivalent to
if false then 1 else (2 + 3), not (if false then 1 else 2) + 3.
The behavior of conditionals is summarized by the following table (v denotes a value).
Examples
if true then 4 else 5 evaluates to 4.if 2 < 3 && 5 < 4 then "blue" else "green" evaluates to "green".if true || "fish" then "yes" else "no" is silent.if false || false then 4+5 else 4+true is silent.if 0 < 5 then 0/5 else 5/0 evaluates to 0.
A variable can be bound to a value. A declaration binds one or
more variables to values. The simplest form of declaration is val, which
evaluates an expression and binds its result to a variable. Declarations follow the rules of
lexical scoping.
val x = 1 + 2 val y = x + xThese declarations bind variable
x to 3 and variable y to 6.
If the expression on the right side of a val is silent, then the variable is not bound, but
evaluation of other declarations and expressions continues. If an evaluated expression depends on that
variable, that expression is silent.
val x = 1/0 val y = 4+5 if false then x else yEvaluation of the declaration
val y = 4+5 and the expression if false then x else y
may continue even though x is not bound. The expression evaluates to 9.
Cor supports two basic data structures, tuples and lists.
A tuple expression is a comma-separated sequence of at least two expressions, enclosed by parentheses. Each expression is evaluated; the value of the whole tuple expression is a tuple containing each of these values in order. If any of the expressions is silent, then the whole tuple expression is silent.
Examples
(1+2, 7) evaluates to (3,7). ("true" + "false", true || false, true && false) evaluates to ("truefalse", true, false).(2/2, 2/1, 2/0) is silent.A list expression is a comma-separated sequence of expressions enclosed by square brackets. It may be of any length, including zero. Each expression is evaluated; the value of the whole list expression is a list containing each of these values in order. If any of the expressions is silent, then the whole list expression is silent.
Examples
[1,2+3] evaluates to [1,5]. [true && true] evaluates to [true].[] evaluates vacuously to [], the empty list.[5, 5 + true, 5] is silent.
There is also a concatenation (cons) operation on lists,
written F:G, where F and G are expressions. Its result is a new list whose first element is the
value of F and whose remaining elements are the list value of G. The : operator is right associative,
so F:G:H is F:(G:H).
Examples
(1+3):[2+5,6] evaluates to [4,7,6].2:2:5:[] evaluates to [2,2,5].t is bound to [3,5]. Then 1:t evaluates to [1,3,5].2:3 is silent, because 3 is not a list.We have seen how to construct data structures. But how do we examine them, and use them? We use patterns.
A pattern is a powerful way to bind variables. When writing val declarations, instead of just
binding one variable, we can replace the variable name with a more complex pattern that follows the structure of the
value, and matches its components. A pattern's structure is called its shape; a
pattern may take the shape of any structured value. A pattern can hierarchically match a value, going deep into its
structure. It can also bind an entire structure to a variable.
Examples
val (x,y) = (2+3,2*3) binds x to 5 and y to 6.val [a,b,c] = ["one", "two", "three"] binds a to "one",
b to "two", and c to "three".
val ((a,b),c) = ((1, true), (2, false)) binds a to 1, b to true,
and c to (2,false).
Patterns are linear; that is, a pattern may mention a variable name at most once.
For example, (x,y,x) is not a valid pattern.
Note that a pattern may fail to match a value, if it does not have the same shape as that value. In a val
declaration, this has the same effect as evaluating a silent expression. No variable in the pattern is bound, and
if any one of those variables is later evaluated, it is silent.
It is often useful to ignore parts of the value that are not relevant. We can use the
wildcard pattern, written _, to do this; it matches any shape and binds no
variables.
Examples
val (x,_,_) = (1,(2,2),[3,3,3]) binds x to 1.val [[_,x],[_,y]] = [[1,3],[2,4]] binds x to 3 and y to 4.
Like most other programming languages, Cor 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+yThe expression on the right of the
= is called the body of the function.
After defining the function, we can call it. A call looks just like the left side of the declaration except that the variable names (the formal parameters) have been replaced by expressions (the actual parameters).
To evaluate a call, we treat it as a sequence of val declarations associating the formal
parameters with the actual parameters, followed by the body of the function.
{- Evaluation of add(1+2,3+4) -} val x = 1+2 val y = 3+4 x+y
Examples
add(10,10*10) evaluates to 110.add(add(5,3),5) evaluates to 13.Notice that the evaluation strategy of functions allows a call to proceed even if some of the actual parameters are silent, so long as the values of those actual parameters are not used in the evaluation of the body.
def cond(b,x,y) = if b then x else y cond(true, 3, 5/0)This evaluates to
3 even though 5/0 is silent, because y is not
needed.
A function definition or call may have zero arguments, in which case we write () for the arguments.
def Zero() = 0
Functions 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)Then,
sumto(5) evaluates to 15.
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 falseThere is no special keyword for mutual recursion; any contiguous sequence of function declarations is assumed to be mutually recursive.
Functions are actually values, just like any other value. Defining a function creates a special value called a closure; the name of the function is a variable and its bound value is the closure. Thus, a closure can be put into a data structure, or bound to some other variable, just like any other value.
def a(x) = x-3 def b(y) = y*4 val funs = (a,b)
Like any other value, a closure can be passed as an argument to another function. This means that Cor supports higher-order functions.
def onetwosum(f) = f(1) + f(2) def triple(x) = x * 3Then,
onetwosum(triple) is triple(1) + triple(2), which is 1 * 3 + 2 * 3 which evaluates to 9.
Since all declarations (including function declarations) in Cor are lexically scoped, these closures are lexical closures. This means that when a closure is created, if the body of the function contains any variables other than the formal parameters, the bindings for those variables are stored in the closure. Then, when the closure is called, the evaluation of the function body uses those stored variable bindings.
Sometimes one would like to create a closure directly, without bothering to give it a name.
There is a special keyword lambda for this purpose. By writing a function
definition without the keyword def and replacing the function name with
the keyword lambda, that definition becomes an expression which evaluates to a closure.
def onetwosum(f) = f(1) + f(2) onetwosum( lambda(x) = x * 3 ) {- identical to: def triple(x) = x * 3 onetwosum(triple) -}Then,
onetwosum( lambda(x) = x * 3 ) evaluates to 9.
Since a function defined using lambda has no name, it is not possible to define
a recursive function in this way. Only def can create a recursive function.
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 allow a new form of pattern which is very useful in clausal definition of functions: a constant pattern. A constant pattern is a match only for the same constant value. We can use this 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) = if (n < 0) then 0 else 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-1), Fibonacci(n)) -} def H(0) = (1,1) def H(n) = val (x,y) = H(n-1) (y,x+y) def fib(n) = if (n < 0) then 0 else ( val (x,_) = H(n) 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) = if (n > 0) then h:(take(n-1,t)) else []
Mutual recursion and clausal definitions are allowed to occur together. Here are two functions,
stutter and mutter, which are each mutually recursive, and each have
multiple clauses. stutter(l) returns l with every odd element repeated.
mutter(l) returns l with every even element repeated.
def stutter([]) = [] def stutter(h:t) = h:h:mutter(t) def mutter([]) = [] def mutter(h:t) = h:stutter(t)
stutter([1,2,3]) evaluates to [1,1,2,3,3].
Clauses of mutually recursive functions may also be interleaved, to make them easier to read.
def even(0) = true def odd(0) = false def even(n) = odd(if n > 0 then n-1 else n+1) def odd(n) = even(if n > 0 then n-1 else n+1)
Cor has two kinds of comments.
A line which begins with two dashes (--), preceded only by whitespace, is a single line comment.
The region from the two dashes to the next encountered newline, inclusive, is ignored.
-- This is a single line comment. -- This is also a single line comment.
Multi-line comments are enclosed by matching braces of the form {- -}.
Multi-line comments may be nested. They may appear anywhere, even in the middle of an expression.
{- This is a multiline comment. -} {- Multiline comments {- can be nested -} -} {- They may appear anywhere, -} 1 + {- even in the middle of an expression. -} 2 + 3