Domanda

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!

È stato utile?

Soluzione

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.

Altri suggerimenti

Yes, a b $ c d is equivalent to (a b) (c d). This is because the dollar sign isn't a syntactical construct: it's an infix operator like every other operator, and has to obey the same rules. It's just that $ applies a function and has very low precedence that gives its behavior.

You asked how to find the precedence of an operator. You can use :info in GHCi:

ghci> :info $
($) :: (a -> b) -> a -> b   -- Defined in `GHC.Base'
infixr 0 $

Note the last line. That says it's a right-associative operator of precedence 0.
(Precedences go from 0 to 9.)

Asking GHCi about : gives the following:

ghci> :info :
data [] a = ... | a : [a]   -- Defined in `GHC.Types'
infixr 5 :

So : is also a right-associative operator, but it's of precedence 5. Since : has a higher precedence compared to $, it'll be grouped into $'s left operand.

Right: you've found out that

a b ! c d $ e f % g h

can be read as putting parens around the expressions to both sides, i.e.

(a b ! c d) (e f % g h)

This holds true precisely as long as you only use stuff you could define yourself in standard Haskell: functions and infix operators1.

Where it doesn't quite work is when you also consider if, case etc. syntax. In particular, and that's where your professor's explanation becomes rather more useful, lambdas are "greedy" to their right:

   \x -> f $ a + x

means \x -> f (a + x), and not (\x -> f) (a + x). But the "parens to both sides" still makes sense, you just mustn't incorporate the lambda arrow:

   \x -> f . g $ a + x

means \x -> (f . g) (a + x), and not \x -> f . g (a + x).


1You could in principle define a custom infix operator with as low precedence as $, but that would be confusing.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top