Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision | |||
pp:high [2021/03/21 08:11] pdmatei [Haskell implementation of foldl] |
pp:high [2021/03/21 08:14] (current) pdmatei |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | |||
===== Higher-order functions ===== | ===== Higher-order functions ===== | ||
Line 89: | Line 88: | ||
A natural mistake is to consider ''((:).f)'' as invalid, since it receives two parameters, and we cannot compose such a function with one receiving only one parameter. However, every function in Haskell receives one parameter and returns **a value** (which can be a function or a primitive value). | A natural mistake is to consider ''((:).f)'' as invalid, since it receives two parameters, and we cannot compose such a function with one receiving only one parameter. However, every function in Haskell receives one parameter and returns **a value** (which can be a function or a primitive value). | ||
- | There are other examples of useful higher-order functions. We illustrate them using examples: | + | There are other examples of useful higher-order functions. We illustrate them using examples, which you should test out: |
<code> | <code> | ||
filter (>3) [1,2,3,4,5,2] | filter (>3) [1,2,3,4,5,2] | ||
zipWith (+) [1,2,3] [3,2,1] | zipWith (+) [1,2,3] [3,2,1] | ||
</code> | </code> | ||
- | |||
Guess what each function does, by testing it on several lists. | Guess what each function does, by testing it on several lists. | ||
- | ==== Haskell implementation of foldl ==== | + | ===== Practice ===== |
- | In Haskell, ''foldl'' is implemented as follows: | ||
- | <code> | ||
- | foldl op acc [] = acc | ||
- | foldl op acc (h:t) = foldl op (op acc h) t | ||
- | </code> | ||
- | Note the call of the ''op'' function. | ||
- | |||
- | ==== An exercise in modular programming with higher-order functions ==== | ||
- | |||
- | Let us consider the task of matrix multiplication in Haskell. Example: | ||
- | <code> | ||
- | 1 2 3 1 0 0 1+3 2 2+3 4 2 5 | ||
- | 4 5 6 x 0 1 1 = 4+6 5 5+6 = 10 5 11 | ||
- | 7 8 9 1 0 1 7+9 8 8+9 16 8 17 | ||
- | </code> | ||
- | |||
- | |||
- | === Matrix representation in Haskell === | ||
- | |||
- | The basic representation building-block in Haskell is the list. We can represent matrices in Haskell as a list where each element is a row (hence a list of elements). Example: | ||
- | |||
- | <code haskell> | ||
- | m = [[1,2,3],[4,5,6],[7,8,9]] | ||
- | </code> | ||
- | |||
- | A good warmup exercise is to write a nice display function for matrices. We transform each element of a line into a string: | ||
- | |||
- | <code haskell> | ||
- | displayline l = map (\e->(show e)++" ") l | ||
- | </code> | ||
- | |||
- | The code can be improved for legibility: | ||
- | <code haskell> | ||
- | displayline = map ((++" ").show) | ||
- | </code> | ||
- | |||
- | |||
- | Next, we fold the list into a string with a newline character: | ||
- | |||
- | <code haskell> | ||
- | displayline l = foldr (++) "\n" (map ((++" ").show) l) | ||
- | </code> | ||
- | |||
- | and simplify again, by expressing the pipeline function calls as functional composition: | ||
- | <code haskell> | ||
- | displayline :: Show a => [a] -> [Char] | ||
- | displayline = (foldr (++) "\n") . (map ( (++ " ") . show ) ) | ||
- | </code> | ||
- | |||
- | Note that we need to explicitly state the type of display. Sometimes, in Haskell, an explicit type declaration is required. For now, we omit details. | ||
- | |||
- | Next, we apply the above process on all matrix lines: | ||
- | |||
- | <code haskell> | ||
- | display :: Show a => [[a]] -> [[Char]] | ||
- | display = | ||
- | let bind = foldr (++) "\n" | ||
- | in bind.(map (bind . (map ((++" ") . show ) ) ) ) | ||
- | </code> | ||
- | |||
- | Notice that we have separated the binding process, because we reuse it. | ||
- | |||
- | Finally, we make all matrices displayable: | ||
- | <code haskell> | ||
- | instance (Show a) => Show [[a]] where | ||
- | show = | ||
- | let bind = foldr (++) "\n" | ||
- | in bind.(map (bind.(map ((++" ").show ) ) ) ) | ||
- | </code> | ||
- | More details about this implementation (e.g. instances, classes) will be given in future lectures. | ||
- | | ||
- | === Matrix multiplication === | ||
- | |||
- | == Step 1: Transposition == | ||
- | |||
- | Matrix multiplication operates on the **lines** of the first matrix and **columns** of the second. We transpose the second matrix, so that we now operate on lines on both matrices. The following code extracts **the first line** from a matrix ''m'': | ||
- | <code haskell> | ||
- | map head m | ||
- | </code> | ||
- | A matrix ''m'' **without** its first column is: | ||
- | <code haskell> | ||
- | map tail m | ||
- | </code> | ||
- | Finally transposition is given by: | ||
- | <code haskell> | ||
- | transpose ([]:_) = [] | ||
- | transpose m = (map head m) : transpose (map tail m) | ||
- | </code> | ||
- | Notice that the //basis case// corresponds to a **list containing empty lists**. | ||
- | |||
- | == Step 2: Computing multiplication == | ||
- | |||
- | To compute the ''i,j''th **element** of the multiplication matrix, we need to multiply per element the ''i''th line by the ''j''th column: | ||
- | <code haskell> | ||
- | zipWith (*) li cj | ||
- | </code> | ||
- | and then add-up the values: | ||
- | <code haskell> | ||
- | foldr (+) 0 (zipWith (*) li cj) | ||
- | </code> | ||
- | |||
- | To obtain the ''i''th **line** of the multiplication matrix, we need to repeat the above process **for each column of the second matrix**, in other words, for each line of its transposition: | ||
- | |||
- | <code haskell> | ||
- | map (\col -> foldr (+) 0 (zipWith (*) li col) ) (transpose m2) | ||
- | </code> | ||
- | |||
- | Finally, to obtain the multiplication matrix, we need to compute all its lines, hence: | ||
- | <code haskell> | ||
- | mult m1 m2 = | ||
- | map (\line -> map (\col -> foldr (+) 0 (zipWith (*) line col) ) (transpose m2) ) m1 | ||
- | </code> | ||
- | |||
- | === Matrices as images === | ||
- | |||
- | A matrix can be used to represent a **rasterized image** (a collection of pixels). In this example, we consider that pixels can have values: ' ' (white), '.' (grey) and '*' (black). | ||
- | |||
- | Higher-order functions can be naturally used to represent image-transformations, for instance, flipping: | ||
- | |||
- | <code haskell> | ||
- | flipH = map reverse | ||
- | flipV = reverse | ||
- | </code> | ||
- | |||
- | Rotations: | ||
- | <code haskell> | ||
- | rotate90left = flipV.transpose | ||
- | rotate90right = flipH.transpose | ||
- | </code> | ||
- | |||
- | The //negative// of an image: | ||
- | <code haskell> | ||
- | invert = map (map (\x->if x=='*' then ' ' else '*') ) | ||
- | </code> | ||
- | |||
- | Scaling an image horizontally: | ||
- | <code haskell> | ||
- | scalex = foldr (\h t->h:h:t) [] | ||
- | </code> | ||
- | |||
- | Scaling vertically: | ||
- | <code haskell> | ||
- | scaley = map scalex | ||
- | </code> | ||
- | |||
- | Balanced scale: | ||
- | <code haskell> | ||
- | scale = scalex . scaley | ||
- | </code> | ||
- | |||
- | Or just a random sequence of operations: | ||
- | <code haskell> | ||
- | rand = foldr (.) id [rotate90left, invert, scale] | ||
- | </code> |