Skip to content

Function composition

Brian Marick edited this page May 1, 2017 · 9 revisions

Problem 1

> List.map ((flip (/) 4) >> negate) [1, 5, 12]
[-0.25,-1.25,-3] : List Float

It's interesting to look at the error messages that come from removing parentheses. Here's the message from removing the outermost one:

> List.map (flip (/) 4) >> negate [1, 5, 12]
The argument to function `negate` is causing a mismatch.

5|                            negate [1, 5, 12]
                                     ^^^^^^^^^^
Function `negate` is expecting the argument to be:

    number

But it is:

    List number

When parsing, >> has a low precedence, so that the line is interpreted as "compose the function produced by List.map ((flip (/) 4) with the function produced by negate [1, 5, 12]."

The error only happens when the compiler encounters negate because List.map ((flip (/) 4) is a perfectly valid function, formed by partially applying List.map. Here's an example of its use:

> divideContentByFour = List.map (flip (/) 4)
<function> : List Float -> List Float
> divideContentByFour [1, 5, 8]
[0.25,1.25,2] : List Float
> List.map divideContentByFour [ [1], [1, 2], [1, 2, 3]]
[[0.25],[0.25,0.5],[0.25,0.5,0.75]] : List (List Float)

To see when the low precedence of >> can be useful, remove the parentheses from around ((flip (/) 4) in our original solution:

> List.map (flip (/) 4 >> negate) [1, 5, 12]
[-0.25,-1.25,-3] : List Float

Here, the partially-applied function is correctly composed with negate. So the parentheses aren't actually needed.

(You might be worried that you'll get the precedence wrong in a way that produces a valid pipeline, just not the one you wanted. I haven't reasoned through the problem to see if it's impossible, but it has to be pretty unlikely. Each step of the pipeline is an expression that produces a function. The value flowing through the pipeline is almost never a function. So a misparenthesization seems like it will always either produce a pipeline stage that's not a function or attempt to feed a value of type A into a function that expects type B. Both those cases must produce an error.)

Problem 2

[1, 5, 12] |> List.map (flip (/) 4) |> List.map negate
[-0.25,-1.25,-3] : List Float

Problem 3

> List.map (negate << (flip (/) 4)) [1, 5, 12]
[-0.25,-1.25,-3] : List Float
> List.map negate <| List.map (flip (/) 4) <| [1, 5, 12]
[-0.25,-1.25,-3] : List Float

Problem 4

List.map ( (4 |> flip (/)) >> negate) [1, 5, 12]

I think it looks awful.

Clone this wiki locally