CS 330: Lecture 26 – Ways of Making Functions: Composition and Lambdas
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
rsortwhich 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
scalebiasto map a value using a linear function:Now we write
scalebias :: Double -> Double -> Double -> Double scalebias scale bias x = scale * x + bias -- without composition scalebias' scale bias = (+ bias) . (* scale) -- with composition
calc2fahrenlike 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:
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)