CS 330: Lecture 26 – Ways of Making Functions: Composition and Lambdas
Dear students:
There are at least four ways of defining functions in Haskell:
- by defining a named abstraction of some algorithm
- by partially applying parameters to an existing function to get a new function expecting only the remaining parameters
- by composing a gauntlet of functions together
- by defining unnamed lambdas
We’ve seen the first two. Now let’s turn to composition. You’ve almost certainly worked with composition in your math classes. What is meant by (f . g)(x)? It means g is evaluated at x, and the result of calling g is used to further evaluate f.
Composing means to chain operations together, much like pipelining at the shell. The difference between composition and pipelining (or Haskell’s $
operator) is a matter of precedence. Haskell’s compose operator lets us chain functions together before applying any parameters. We can produce new functions by composing old ones together:
h = f . g
h 5 -- these three calls
(f . g) 5 -- will produce the
f (g 5) -- same result
That means we can eliminate a lot of clutter from our code! Those gauntlets of function calls that our data must pass through can be shrunk down to a single meaningful name.
Time for some examples:
- Let’s write
rsort
which sorts a list in reverse:rsort = Ord a => [a] -> [a] rsort = reverse . sort
- Let’s write a function that gives us the second element of a list, the neck:
neck :: [a] -> a neck = head . tail
- Let’s write a function to compute the square of the sum of a list:
squareOfSum = (^2) . sum
- Let’s write a function to compute the number of negatives in a list:
nnegatives :: [Integer] -> Int nnegatives = length . filter isNegative where isNegative x = x < 0
- Let’s write
scalebias
to map a value using a linear function:Now we writescalebias :: Double -> Double -> Double -> Double scalebias scale bias x = scale * x + bias -- without composition scalebias' scale bias = (+ bias) . (* scale) -- with composition
calc2fahren
like we should have back in high school:c2f :: Double -> Double c2f = scalebias' 1.8 32
- Let’s write a function that gives us a list of the unique items within another list:
onlies :: Ord a => [a] -> [a] onlies = concat . filter single . group . sort where single list = length list == 1
- Let’s write a function that gives us the mode in a list of data:
onlies :: Ord a => [a] -> a onlies = head . head . reverse . sortWith length . group . sort
Both partial evaluation and composition eliminate a lot of the overhead that we programmers find ourselves doing. Much of our development life is spent gluing together abstractions into some mega-abstraction, and these two techniques give us some of our life back.
See you then!
P.S. It’s time for a haiku!
P.P.S. Here’s the code we wrote together:
lecture.hs
import Data.List
import Data.Char
toLowercase :: String -> String
toLowercase "" = ""
toLowercase (first:rest) = toLower first : toLowercase rest
tango :: Ord a => (a, a) -> a
tango (a, b) = max a b
rsort :: Ord a => [a] -> [a]
-- rsort list = reverse (sort list)
-- rsort list = (reverse . sort) list
rsort = reverse . sort
neck :: [a] -> a
-- neck list = (head . tail) list
neck = head . tail
nnegatives :: [Int] -> Int
nnegatives = length . filter (< 0)