Introduction to Functional Languages
What does functional mean? Two main senses:
Programs consist of functions with no side effects.
Functions are supported as ``first-class'' values.
Grand Claim: functional programs are:
easier to get right;
easier to test;
easier to transform;
easier to parallelize;
easier to prove things about;
Lisp, Scheme (``strict'', dynamically typed, impure)
Standard ML, CAML (``strict'', statically typed, impure)
Haskell, Gofer (``lazy'', statically typed, pure)
Programs are constructed by combining functions via composition; the output of one function is passed to the input of another.
Since there are no side-effects, all interaction between program components must be by passing explicit arguments and results. There are no shared variables or common data areas.
This means each function can stand on its own. It can be tested and debugged independently of any other functions.
(Also, since it's a pain to communicate information explicitly this way, programmers have an incentive to avoid unnecessary coupling between components - a sound software engineering goal.)
What is a function?
Abstractly, a function is just a mapping from a set of possible input values (the domain) to a set of possible output values (the co-domain).
Function can be specified intensionally, by means of a rule that describes how to calculate the output for a given input, or extensionally, by means of a (perhaps infinite) table that describes what the output is for any given input.
Both views are extremely important. Intensional view allows us to program a machine; extensional view allows us to abstract away from the details of the program by focusing on the externally visible behavior.
Functional languages allow us to manipulate functions as just another kind of data. This often allows us to take a much higher-level, declarative approach to programming problems.
Standard ML is one of the best-developed examples of a functional language.
Name originally derived from ``Meta Language;'' developed in Edinburgh in early 1980's.
Uses so-called ``eager'' evaluation, like LISP/Scheme; more efficient (though less powerful) than so-called ``lazy'' languages such as Haskell.
Strongly typed at compile time, unlike LISP/Scheme.
Not doctrinaire; supports some side-effecting features for I/O, updatable store.
Has good compilers and interpreters, including SML of New Jersey and Moscow ML.
Has a full formal operational semantics.
SML provides a interactive ``read-eval-print'' loop for incremental program development.
You type value or function definitions; they are type-checked and compiled or interpreted, and are then usable in further definitions.
In Standard ML of New Jersey:
Interactive System (continued)
Note that these are declarations binding constants to identifiers; they are not assignment statements! Redefining an identifier completely replaces the old binding.
In Moscow ML, similar except for form of type error message:
Notice that, unless there is a type inconsistency, the system was able to infer the types of the declared identifiers automatically. In fact, SML almost never needs you to specify a type explicitly, although you always may:
Loading from Files
Of course, it's often handier to write code using an auxiliary editor. You can then compile your code using copy-and-paste, or via the use command. For example, if fred.sml contains the lines:
then we can load these definitions into the read-eval-print loop as follows:
Note that the contents of the file are not echoed; just the ``results.''
Functions are (usually) defined using the fun keyword:
Calling (``applying'') a function is specified just by writing the function name followed by the argument; no parentheses are needed (though they are generally harmless).
All ML functions take exactly one argument; we'll see ways to get the effect of multiple arguments later.
Function definitions and applications must be well-typed too:
Note that faulty function definitions are flagged as soon as the function is defined, rather than waiting until it is applied.
Pairs and Tuples
SML has a built-in type of pairs. You can pair together any two values. In fact, SML supports triples, quads, , arbitrary n-tuples.
Useful for passing multiple arguments to a function, or returning multiple results.
In fact, the built-in binary operators like + are really just pre-defined functions that take a pair argument: a+b is a shorthand for (op +)(a,b).
SML also has a built-in data type of lists, i.e., sequences of zero or more values. You can make lists of anything, but everything in a given list must have the same type.
Lists have two interchangeable representations. The first derives from the recursive definition of what a list can be:
A list can be empty, in which case it is represented by nil.
A list can be formed by adding a value x onto the head of an existing list y, in which case it is represented by x::y, read ``x cons y''.
We can construct any list this way:
The other way to write a list is to write the elements within square brackets, separated by commas, as illustrated in the values echoed by the interactive system. This is just a shorthand; in general,
We can also use :: and nil as patterns in case expressions that ``deconstruct'' or analyze the contents of a list to obtain its constituent components.
Recursive and Polymorphic Functions
Most useful functions on lists are recursive:
SML actually supports arbitrary user-defined recursive datatypes including queues, trees, etc.; the built-in list type is just a special case.
The 'a in the type of length is a type variable: it indicates that the function can be applied to lists of any kind, as illustrated. This powerful feature is called polymorphism; it helps enable code re-use.
Here's another function on lists:
Note the similarity in form to length. We can take advantage of this similarity to abstract the recursive pattern common to these functions and write them both as instances of a single higher-order function: