0% found this document useful (0 votes)
2 views12 pages

Lecture 18 - Higher Order Functions II

Higher Order Functions 2

Uploaded by

DA BEST
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views12 pages

Lecture 18 - Higher Order Functions II

Higher Order Functions 2

Uploaded by

DA BEST
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

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.

You might also like