Skip to content

Local Definitions

Before we look at recursion, a functional language's only way to repeat things, let me introduce you to an important syntactic construct in Haskell: local definitions, ones that are visible only inside some surrounding function.

As almost all modern programming languages, Haskell is a statically scoped programming language. This means that the program is composed of nested code blocks, called scopes, and every definition of a variable or function inside one of these scopes is visible only inside this scope or in scopes nested within it. In C, C++, Java or Rust, these code blocks would be delimited by curly brackets. In Python and Haskell, code blocks are mostly indicated by indentation and correspond to natural components of your program.

The outermost scope is the module, the entire program file.1 Each function definition at the module level defines a scope nested within this outermost scope. You can nest other definitions inside function definitions, and you can do this recursively to arbitrary depth. Each of these local definitions produces a nested scope. Remember,

A definition in a given scope is visible only inside that scope or in scopes nested within it.

Let's see how to create such local definitions. Let's start with a simple Python function to compute the length of a 2-d vector:

def veclen(x, y):
    x2 = x * x
    y2 = y * y
    return sqrt(x2 + y2)

This function introduces two local variables, x2 and y2. These variables exist for exactly as long as the function call is active, inside the function call's stack frame. Moreover, any function that veclen calls recursively—here this is only the built-in sqrt function—has no access to x2 and y2; they are local to veclen. Local definition provide a sense of encapsulation.

I mentioned before that programming by function composition technically makes the definition of local variables unnecessary, but at the same time, programming that way would feel terribly awkward. Thus, Haskell provides a mechanism that allows us to define local variables. In fact, there are two such mechanisms: let and where. They are almost always equivalent, and choosing between them is mostly a matter of personal preference. Most Haskell programmers prefer to use where over let.


  1. A properly formatted Haskell program starts with the clause module ModuleName where, which names the module in this file ModuleName. In this case, the file should be called ModuleName.hs. Otherwise, the compiler complains about a mismatch between the module name and the file name. So far, we have hardly used program files at all, and when we did, we loaded them into GHCi using :load. In this case, it is permissible to omit the module header. This implicitly names the module Main, and no check is performed that the file name matches the module name. We'll continue this practice for now, but we will write multi-file programs later, composed of multiple modules. Those must then be proper modules, so we can import them into other modules using Haskell's import statement.