Bottling Computation Patterns

Polymorphism and HOFs are the Secret Sauce

Refactor arbitrary repeated code patterns …

… into precisely specified and reusable functions











EXERCISE: Iteration

Write a function that squares a list of Int

When you are done you should see











Pattern: Iteration

Next, lets write a function that converts a String to uppercase.

Recall that in Haskell, a String is just a [Char].

Hoogle to see how to transform an individual Char











Iteration

Common strategy: iteratively transform each element of input list

Like humans and monkeys, shout and squares share 93% of their DNA

Super common computation pattern!











Abstract Iteration “Pattern” into Function

Remember D.R.Y. (Don’t repeat yourself)

Step 1 Rename all variables to remove accidental differences

Step 2 Identify what is different

  • In squares we transform x to x * x

  • In shout we transform x to Data.Char.toUpper x

Step 3 Make differences a parameter

  • Make transform a parameter f

Done We have bottled the computation pattern as foo (aka map)

map bottles the common pattern of iteratively transforming a list:

Fairy In a Bottle
Fairy In a Bottle











QUIZ

What is the type of map ?

A. (Int -> Int) -> [Int] -> [Int]

B. (a -> a) -> [a] -> [a]

C. [a] -> [b]

D. (a -> b) -> [a] -> [b]

E. (a -> b) -> [a] -> [a]











The type precisely describes map

That is, map takes two inputs

  • a transformer of type a -> b
  • a list of values [a]

and it returns as output

  • a list of values [b]

that can only come by applying f to each element of the input list.









Reusing the Pattern

Lets reuse the pattern by instantiating the transformer

shout

squares









EXERCISE

Suppose I have the following type

Use map to write a function

such that

















The Case of the Missing Parameter

Note that we can write shout like this

Huh. No parameters? Can someone explain?












The Case of the Missing Parameter

In Haskell, the following all mean the same thing

Suppose we define a function

Now the following all mean the same thing

Why? equational reasoning! In general

as long as x doesn’t appear in e.

Thus, to save some typing, we omit the extra parameter.












Pattern: Reduction

Computation patterns are everywhere lets revisit our old sumList

Next, a function that concatenates the Strings in a list












Lets spot the pattern!

Step 1 Rename

Step 2 Identify what is different

  1. ???

  2. ???

Step 3 Make differences a parameter












EXERCISE: Reduction/Folding

This pattern is commonly called reducing or folding

Can you figure out how sumList and catList are just instances of foldr?












Executing foldr

To develop some intuition about foldr lets “run” it a few times by hand.

Look how it mirrors the structure of lists!

  • (:) is replaced by op
  • [] is replaced by base

So












Typing foldr

foldr takes as input

  • a reducer function of type a -> b -> b
  • a base value of type b
  • a list of values to reduce [a]

and returns as output

  • a reduced value b












QUIZ

Recall the function to compute the len of a list

Which of these is a valid implementation of Len

A. len = foldr (\n -> n + 1) 0

B. len = foldr (\n m -> n + m) 0

C. len = foldr (\_ n -> n + 1) 0

D. len = foldr (\x xs -> 1 + len xs) 0

E. All of the above












The Missing Parameter Revisited

We wrote foldr as

but can also write this

Can someone explain where the xs went missing ?












Trees

Recall the Tree a type from last time

For example here’s a tree












Some Functions on Trees

Lets write a function to compute the height of a tree

Here’s another to sum the leaves of a tree:

Gathers all the elements that occur as leaves of the tree:

Lets give it a whirl












Pattern: Tree Fold

Can you spot the pattern? Those three functions are almost the same!

Step 1: Rename to maximize similarity

Step 2: Identify the differences

  1. ???
  2. ???

Step 3 Make differences a parameter












Pattern: Folding on Trees

Lets try to work out the type of tFold!










QUIZ

Suppose that t :: Tree Int.

What does tFold (\x y z -> y + z) 1 t return?

a. 0

b. the largest element in the tree t

c. the height of the tree t

d. the number-of-leaves of the tree t

e. type error










EXERCISE

Write a function to compute the largest element in a tree or 0 if tree is empty or all negative.










Map over Trees

We can also write a tmap equivalent of map for Trees

which gives










EXERCISE

Recursion is HARD TO READ do we really have to use it ?

Lets rewrite treeMap using tFold !

When you are done, we should get










Examples: foldDir

foldDir takes as input

  • an accumulator f of type [a] -> r -> DirElem a -> r

    • takes as input the path [a] , the current result r, the next DirElem [a]

    • and returns as output the new result r

  • an initial value of the result r0 and

  • directory to fold over dir

And returns the result of running the accumulator over the whole dir.










Examples: Spotting Patterns In The “Real” World

These patterns in “toy” functions appear regularly in “real” code

  1. Start with beginner’s version riddled with explicit recursion.

  2. Spot the patterns and eliminate recursion using HOFs.

  3. Finally refactor the code to “swizzle” and “unswizzle” without duplication.

Try it yourself

  • Rewrite the code that swizzles Char to use the Map k v type in Data.Map










Which is more readable? HOFs or Recursion

At first, recursive versions of shout and squares are easier to follow

  • fold takes a bit of getting used to!

With practice, the higher-order versions become easier

  • only have to understand specific operations

  • recursion is lower-level & have to see “loop” structure

  • worse, potential for making silly off-by-one errors

Indeed, HOFs were the basis of map/reduce and the big-data revolution

  • Can parallelize and distribute computation patterns just once

  • Reuse across hundreds or thousands of instances!