Search code examples
haskelldollar-sign

Why doesn't this dollar-sign construct work?


Yep, another dollar-sign question. I'm sorry... (I used the search function!)

My professor for the course functional programming told us that the dollar sign 'kinda adds an opening parenthese and then a closing one at the end' (it's very roughly described here in more or less the same manner). So

fibs = 0 : 1 : zipWith (+) fibs $ tail fibs

should be equivalent to

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

Well, it isn't. The second thing compiles fine, the first thing gives an error:

jkjj.hs:1:8:
    Couldn't match expected type `[a1] -> [a1]' with actual type `[a0]'
    The first argument of ($) takes one argument,
    but its type `[a0]' has none
    In the expression: 0 : 1 : zipWith (+) fibs $ tail fibs
    In an equation for `fibs':
        fibs = 0 : 1 : zipWith (+) fibs $ tail fibs

fibonacci.hs:1:16:
    Couldn't match expected type `[a0]' with actual type `[a1] -> [a1]'
    In the return type of a call of `zipWith'
    Probable cause: `zipWith' is applied to too few arguments
    In the second argument of `(:)', namely `zipWith (+) fibs'
    In the second argument of `(:)', namely `1 : zipWith (+) fibs'

And of course, since $ is a function, things like:

fibs = 0 : 1 $ zipWith (+) fibs (tail fibs)

won't work, so at the very least the explanation my professor gave was an oversimplification. While writing this post, I tried to place the parentheses so that the error would be the same. I got:

fibs = (0 : 1 : zipWith (+) fibs) $ tail fibs

and

fibs = (0 : 1 : zipWith (+) fibs) (tail fibs)

which both gave me exactly the same error message (except for the column numbers, of course). Why is this? Is a b $ c d equivalent to (a b) (c d) rather than a b (c d)? I think this all has to do with function precedence and/or associativity, but I don't know the specifics. I don't know how you can see what precedence level a function has (except trying a lot of combinations) and I can't find it with google either.

I hope somebody can help me figure this out!


Solution

  • This is a precedence issue. You have two infix operators here, : and $. As an infix operator, : has higher precedence than $, so it binds more tightly. You can ask about precedence in ghci

    >> :i :
    data [] a = ... | a : [a]        -- Defined in `GHC.Types`
    infixr 5 :
    
    >> :i $
    ($) :: (a -> b) -> a -> b
    infixr 0 $
    

    The infixr means that the operator groups to the right (so that the expression a + b + c is interpreted as a + (b + c)) and the number gives the precedence (higher = more tightly binding).

    Also, you need to know that function application has the highest precedence (tighest binding). So in your two expressions, this one

    fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
    

    is grouped like

    fibs = 0 : (1 : (zipWith (+) fibs (tail fibs)))
    

    whereas this

    fibs = 0 : 1 : zipWith (+) fibs $ tail fibs
    

    is grouped like

    fibs = (0 : (1 : (zipWith (+) fibs))) $ (tail fibs)
    

    which gives you an error, because the expression to the left of $ should be a function, but in your case it is a list.