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
.
-
A properly formatted Haskell program starts with the clause
module ModuleName where
, which names the module in this fileModuleName
. In this case, the file should be calledModuleName.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 moduleMain
, 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'simport
statement. ↩