Further Programming Paradigms
Lecture 18: Higher Order Functions II (Lambda Abstractions)
Dr Martin Barrere, University of Surrey
(Slides designed by Prof. Paul Krause, University of Surrey)
Lambda abstractions
• We have already seen that once we have defined a function, we can use it in
application. For example:
map addOne [2, 3, 4]
• (assuming we have defined addOne x = x +1)
• Lamda calculus (the mathematical foundation of FP) allows us to write a
function down directly, without giving it a name. In Haskell:
map (\x -> x+1) [2, 3, 4]
• The expression (\x = x+1) is an example of a lambda abstraction
This is leading us into λ-Calculus - the mathematical
formalism that underpins Functional Programming
Lambda expressions
• These relate back to the “lambda calculus” which is the mathematical
formalism that underpins functional programming.
• These are finding their way into most imperative languages as a succinct
notation for expressing “anonymous functions” - functions that are defined
locally in some context but don’t need to be referred to explicitly by name
• We can, however, also name Lambda expressions if we want to use this
style of defining functions
• In a maths book, you might see:
(𝜆𝑥. 2 ∗ 𝑥 + 1)3 = 2 ∗ 3 + 1 = 7
• In Haskell, you can write:
Prelude> (\x -> 2*x + 1) 3
7
Defining functions using lambda expressions
These two are equivalent:
add :: Int -> Int -> Int
add x y = x + y
addl :: Int -> (Int -> Int)
addl = \x -> (\y -> x + y)
Defining functions using lambda expressions
These two are equivalent:
add :: Int -> Int -> Int
add x y = x + y
addl :: Int -> (Int -> Int)
addl = \x -> (\y -> x + y)
I could also write:
addlam :: Integer -> Integer -> Integer
addlam = \x y -> x + y
More formally:
If M[x] is an expression containing (‘depending on’) x, then
λx.M[x]
denotes the function x -> M[x]
• Read: “Take a value assigned to x and return the evaluation of M with all
instances of x replaced with that value assignment”
• This is known as abstraction
• Essentially the lambda abstraction is simply explicitly identifying the variables
that must be assigned values in an expression
Application, abstraction and the λ-Calculus
• Application (we have seen this a lot):
F A
• denotes the data F (considered as algorithm) applied to A (considered as input)
• Combine this with abstraction and we get, for example:
(λx.2*x + 1)3 = 2*3 + 1 (=7)
• Denoting the function x -> 2*x + 1 applied to the argument 3
• In general:
(λx.M)N = M[x:=N] ——————— (β)
• where [x:=N] denotes substitution of N for all occurrences of x
• So, two operations (application and abstraction) and one axiom (β) capture the
essence of the λ-Calculus
• After this, it gets more complex … 🤯
Back to Haskell and Functional Programming
Example use of lambda abstraction
• Suppose we want to take a list of functions and apply them all to a particular
argument (think games, and evaluating the outcome of a list of possible
moves):
mapFuns :: [a -> b] -> a -> [b]
Two alternatives:
mapFuns [] x = []
mapFuns (f:fs) x = f x : mapFuns fs x
Or
mapFuns fs x = map (\f -> f x) fs
• The function (\f -> f x) depends on the value of x, so cannot be defined
as a top level function. Instead, we provide an abstraction of function
application and apply that to each element of the list of functions.
Partial application of functions
This is important in Haskell programming as it enables us to provide
specialisations of general operations (Ruby does this rather clumsily with the
yield statement). E.g.
flipV = map reverse
beside = zipWith (++)
Essentially, in Haskell every function takes exactly one argument. If this
application returns a function, then this function may be applied to a further
argument, and so on.
This is the curried representation of functions
Curried functions and function arguments
• Consider, for example, a multiplication function:
multiply :: Int -> Int -> Int
• This is shorthand for
multiply :: Int -> (Int -> Int) -- (-> is right associative)
• So, for example:
multiply 2 :: Int -> Int
• Which can in turn be applied to give
(multiply 2) 5 :: Int -- (application is left associative)
• Which can hence be written as:
multiply 2 5 :: Int
• You can, of course, switch between the curried, f x y, and uncurried
representations, f (x, y), representations but the curried form is neater
(fewer parentheses) and makes the support for partial application explicit.