Home  Up

Functional-Programming Tutorial and HowTo

by Dan Bensen

A random list of tricks and techniques for doing functional programming.

eval vs. exec (what vs. how)
small, simple functions
returning from the middle of a function
lists and list functions
looping sequencing
closures
monad functions in Haskell

eval vs. exec
or
what vs. how
Traditionally, the word "programming" has been synonymous with "telling a computer what to do". In other words, the programmer instructs the computer as to what procedures it should execute. In reality, programming does not have to work this way. The programmer can communicate with the computer in many different ways and at many different levels. In particular, you can indicate generally what result you have in mind without specifying how to achieve it. This is telling a computer what you want.

The model of functional programming is mathematics: The computer computes values by evaluating mathematical or quasi-mathematical expressions. The programmer doesn't worry about how these expressions are evaluated, he or she only tells the computer what the expressions are. So rather than executing statements, a functional program evaluates expressions. This gets a little tricky when the program has to interact with the real world, but with a few adjustments and compromises, it can work well.

As a consequence of the mathematical model, everything in a functional program returns a value. There's no "return" statement, because you don't tell the computer to return a value. You simply tell the computer what value a function returns, and the computer returns it. For instance, you would write something like
double(x) = 2*x
instead of
double(x) = return 2*x
The "return" is implied.

small, simple functions One of the basic tools of functional programming is to break large, complicated functions into smaller, simpler ones. This makes testing and debugging easier, and any good functional compiler will optimize the code well, as long as the code doesn't contain side effects or mutable state.
return To return from the middle of a function, you have to use if-else or some other branching construction, but you don't need "return":
if readyToReturn args then x
else
  ...
This says that, if the test is true, then the value of the function is x, otherwise it's whatever the rest of the function evaluates to. If this if-else is nested inside some larger expression, then it can't return from the function directly. It can only return a value to be processed by some outer expression that can return its own value, and so on, until the outermost expression in the function returns a value.
list functions Lists and list functions are used often in functional programming. To operate on several values, put them into a list and map the function onto the list. To remove some of the values, filter the list through a test function. To combine them into one result, use one of the fold functions.

looping with recursion Technically speaking, recursion isn't quite as concise as a loop statement for simple looping. So why don't we just use loops?

Anything you can do with a loop, you can also do with recursion. In fact, pure recursion can even do some things normally associated with looping that require keywords: breaking out of the loop, continuing to the next iteration, and retrying the current one. It also makes it easy to do much more difficult things.

Recursion is so important it has its own page.
break/last To break out of a recursive loop, return a value instead of calling the function again.
myLoop n =
  ...
  if doneWithLoop args then x
  else
    ...
next/continue To continue to the next iteration from the middle of the function, return the value of another recursive call with the next value of the looping parameters.
myLoop n =
  ...
  if doneWithThisIteration args then myLoop (n+1)
  else
    ...
or
myLoop n =
  let next = myLoop (n+1)
  ...
redo/retry The Ruby "retry" command can be emulated by calling the function with the same arguments. You might want to define a local function that remembers them. For example, in OCaml:
let foo x y z =
  let retry = foo x y z
  in
  ...
Note that this won't do you any good in pure functional code, which is why the example isn't written in Haskell. In a pure function, every call will do the same thing. In order for retry to eventually return a result, there has to be some nonfunctional impurity in the function, for instance a random number generator or some external input stream.

sequencing You can sneak some sequencing into a function with multiple "let" bindings (let* in Common Lisp). In OCaml:
let y = f x in
let z = g y in ...
But if you don't need to bind variables, the "right" way to do it is, as usual, with function calls:
let z = g (f x)
In both cases, f is guaranteed to be evaluated before g. In an impure language like OCaml, you can put I/O commands in the functions for debugging, at the cost of performance. In Haskell, you would have to use monads.
closures
  • First of all, a closure is a function.
  • Specifically, it's a function that's defined locally inside another function1, but is returned by that function and used somewhere else in the program.

A closure contains values that were defined outside the closure itself, but inside the local scope of the outer defining function:

makeAccumulator i = (\n -> n+i)

makeAccumulator returns an anonymous function—this is the closure—of n that uses i as though it were a global. The closure "closes over" i, which means that it uses i even after makeAccumulator has returned, despite the fact that i was introduced as a local variable in makeAccumulator. This function is typical in that the closure is often anonymous.

The point of the closure is that values like i are accessible to the closure even after the outer function it was defined in has exited. In a traditional imperative language, this wouldn't happen. Variables local to the outer function would disappear when the function exits, because they're popped off of the call stack. If a closure is created, the values have to stay in existence. The variables are allocated on the heap, and are still visible to the closure when it's called. To create a closure, define a local function that uses one or more local values from the calling function.

Haskell monad functions
  • If you're using do notation in a monad function, don't type "in" after a "let" binding.
  • You can use control expressions like if & case.
  • You don't need "do" or other punctuation for a single action.
  • The return value of a monad function can be either a "return", or the return value of another monad function called in the outer function.

1A closure can also be defined in an anonymous lexical scope.

© 2007 Dan Bensen   Home   About  Site map