Functional programming languages are all based on the lambda calculus. Some languages in this general category incorporate features into the language that aren’t translatable into lambda expressions. Haskell is a pure functional language.
The word purity
in functional programming is sometimes also used to mean what is more
properly called referential transparency
. Referential transparency means that the
same function, given the same values to evaluate, will always return the same result
in pure functional programming, as they do in math.
Scala is an objective oriented language, which incorporates functional programming features.
Function
A function is a relation between a set of possible inputs and a set of possible outputs. The function itself defines and represents the relationship.
Lambda’s structure
The lambda calculus has three basic components, or lambda terms
: expressions, variables,
and abstractions.
The expression
refers to a superset of all those things: an expression can be a variable
name, an abstraction, or a combination of those things. The simplest expression is a
single variable.
Variable
s here have no meaning or value, they are names for potential inputs to functions.
An abstraction
is a function
. It is a lambda term that has a head (a lambda) and a body
and is applied to an argument. An argument
is an input value.
Abstractions consisit of two parts: the head
and the body
. The head of the function is
a \(\lambda\) followed by a variable name. The body of the function is another expression.
Following is a simple function:
The variable named in the head is the parameter
and bind
s al instances of that same
variable in the body of the function. The .
separates the parameters of the lambda from
the function body. It is the identity
function.
We note that the lambda abstraction \(\lambda x.x\) has no name. It is an anonymous
function
. A named function can be called by name by another function; an anonymous
function cannot.
Reduction
The meaning of lambda expressions is defined by how expressions can be reduced.
There are three kinds of reduction:
- \(\alpha\)-conversion: changing bound variables (alpha);
- \(\beta\)-reduction: applying functions to their arguments (beta);
- \(\eta\)-conversion: which captures a notion of extensionality (eta).
We also speak of the resulting equivalences: two expressions are \(\beta\)-equivalent, if they can be \(\beta\)-converted into the same expression, and \(\alpha\)/\(\eta\)-equivalence are defined similarly.
The term redex
, short for reducible expression, refers to subterms that can be reduced by one of
the reduction rules. The expression to which a redex reduces is called its reduct
.
Alpha conversion
In the abstraction \(\lambda x.x\), the variable \(x\) is not semantically meaningful except
in its role in that single expression. There’s a form of equivalence between lambda terms
called alpha equivalence
. That says,
all mean the same thing. They are all the same function.
The alpha conversion
, sometimes known as alpha renaming
, allows bound variable names to be
changed. Terms that differ only by alpha conversion are called \(\alpha\) equivalent.
Frequently, in uses of lambda calculus, \(\alpha\) equivalent terms are considered to be
equivalent.
Beta reduction
Beta-reduction is defined in terms of substitution: When we apply a function to an argument, we
substitute the input expression for all instances of bound variables within the body of the
abstraction, and eliminate the head of the function as well, since its only purpose was to bind a
variable. This process is called beta reduction
.
Applications in the lambda calculus are left associative
.
The purpose of the head of the function is to tell us which variables to replace when we apply our function, that is, to bind the variables. A bound variable must have the same value throughout the expression.
Free variables
Sometimes the body expression has variables that are not named in the head. Those variables are
called free variables
.
Multiple arguments
Each lambda can only bind one parameter and can only accept one argument. Functions that require
multiple arguments have multiple, nested heads. When you apply it once and eliminate the first
(left most) head, the next one is applied, and so on. This formulation is commonly called currying
.
The following
is a convinent shorthand for two nested lambdas:
And also note that in the following lambda:
The substitution process can become a tangle of x
s that are not the same x
because each was
bound by a different head.
With free variables and multiple parameters, the basic process of beta reduction will remain the same. The process of beta reduction stops when there are either no more heads, or lambdas, left to apply or no more arguments to apply functions to.
The beta normal form
is when you cannot beta reduce (apply lambdas to arguments) the terms any
further. This corresponds to a fully evaluated expression, or, in programming, a fully excuted
program.
Eta conversion
Eta-conversion expresses the idea of extensionality, which in this context is that two functions are the same if and only if they give the same result for all arguments. Eta-conversion converts between \(\lambda x.(f x)\) and \(f\) whenever \(x\) does not appear free in \(f\).
Following is an exmple in Scala:
1
2
3
4
5
def sum(x: Int, y :Int): Int = x + y
val sumFunc = sum
// or val sumFunc = sum(_: Int, _: Int)
Combinators
A combinator
is a lambda term with no free variables. Combinators, as the name suggests, serve
only to combine
the arguments it is given.
Divergence
Not all reducible lambda terms reduce neatly to a beta normal form. This isn’t because they’are
already fully reduced, but rather because they diverge
. Divergence here means that the reduction
process never terminates or ends. Reducing terms should ordinarily converge
to beta normal form,
and divergence is the opposite of convergence, or normal form. Following is an example of a lambda
term called omega
that diverges:
This matters in programming because terms that diverge are terms that don’t produce an answer
or
meaningful result.
Haskell
There things all apply to Haskell, as they do to any pure functional languages, because semantically
Haskell is a lambda calculus. Haskell is a typed lambda calculus
with a lot of surface-level
decoration sprinkled on top, but the semantics of the core language are the same as the lambda
calculus.
The meaning of Haskell programs is centered around evaluating expressions rather than executing instructions, although Haskell has a way to execute instructions too.
Scala
Well, Scala uses objects to simulate lambda calculus.