Why the result of evaluating `init . cuts [1,2,3]` is different from `(init . cuts) [1,2,3]`?

StackOverflow https://stackoverflow.com/questions/23500852

  •  16-07-2023
  •  | 
  •  

Question

I'm trying to understand why this two evaluations: (init . cuts) [1,2,3] and init . cuts [1,2,3] are different, where:

cuts :: [a] -> [([a],[a])]
cuts xs = zipWith splitAt [0..length xs] (repeat xs)

The first gives the result that I expected: [([],[1,2,3]),([1],[2,3]),([1,2],[3])], but the second returns this error:

<interactive>:389:8:
    Couldn't match expected type `a0 -> [a1]'
                with actual type `[([a2], [a2])]'
    In the return type of a call of `cuts'
    Probable cause: `cuts' is applied to too many arguments
    In the second argument of `(.)', namely `cuts [1, 2, 3]'
    In the expression: init . cuts [1, 2, 3]

I assume that init . cuts [1,2,3] = init (cuts[1,2,3]), is this correct?

Thanks,
Sebastián.

Was it helpful?

Solution

This is because of fixity, or order of operations. When the compiler sees

(init . cuts) [1, 2, 3]

It parses this as the function inits . cuts with the argument [1, 2, 3]. When it sees

init . cuts [1, 2, 3]

It parses this as the function inits composed with the function cuts [1, 2, 3], but since cuts [1, 2, 3] is not a function, it raises a type error. This occurs because function application always takes higher precedence over operator application. That's why you can write expressions like

f = zipWith (+) [1..] . filter (< 10) . take 5

instead of

f = (zipWith (+) [1..]) . (filter (< 10)) . (take 5)

If operator precedence could be higher than function application precedence, then you might have to use the latter form depending on the operator precedence, and then expressions become much harder for the compiler and for programmers to parse. It could mean that there might be ambiguity with something like

x 1 <#> y

Since it could be parsed as

(x 1) <#> y

Or

x (1 <#> y)

Depending on precedence (I just made up an operator <#>).


As @chepner rightly points out, if you want to avoid parentheses (as many Haskellers do), you can instead use the $ operator, which has a carefully chosen fixity to allow you to do

init . cuts $ [1, 2, 3]

The rule of thumb with $ operator is that it is the same has the same effect1 as taking whatever appears to the right and wrapping it in parentheses, so a complex expression like

func (func (func (func (func 1))))

Can instead be written in the much nicer form

func $ func $ func $ func $ func 1

Or even

func . func . func . func . func $ 1

1 As @Cubic has noted, some may think that $ is some special syntax of Haskell. This is not the case. It is defined very simply as

($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $

In fact, this function is identical to the id function, just expressed as an infix operator with very low precedence associating to the right, and that fixity is what makes the operator useful for expressing code very cleanly. It can be equivalent to using more parentheses, but that does not make it an alternate syntax to them. It's just another function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top