How to understand this `$` usage in Haskell

409 views Asked by At

This happens in the situation you want to apply bunch of functions to the same variable, it may look like this:

map (\f->f 4) [odd, even]

but from LYAH using $ make it very neat

map ($ 4) [odd, even]

why does it work. first I type it in ghci like $ 4 odd, it failed, then I type ($ 4) odd, which works fine. then I check the type of ($ 4) using :t which shows ($ 4) :: Num a => (a -> b) -> b, odd is odd :: Integral a => a -> Bool. It seems make sense, but still not clear to me.

Can anyone explain it clearly, is it another common usage of $, and is there other more usage of $.

3

There are 3 answers

1
Shoe On

Anatomy of the operator

The $ application operator is in the form:

($) :: (a -> b) -> a -> b

It's often seen in situation when you want to avoid a trailing pair of parenthesis:

func a (b + c)

is equal to:

func a $ b + c

The magic behind this is simply explained in its fixity declaration:

infixr 0

This means: everything that is after $ will be grouped into a single entity, just like if they where enclosed in parenthesis.

Of course this can be also "nested" like so:

func a $ b + other $ c - d

which means:

func a (b + other (c - d))

Application operator as function

Your case is very interesting and, in my experience, not used very often.

Let's analyze this:

map ($ 4) [odd, even]

We know that map's type is:

map :: (a -> b) -> [a] -> [b]

The behavior, if someone forgot, is: take the first argument (a function from a to b) and apply it to every a in the second argument list, finally return the resulting list.

You can see ($ 4) as "pass 4 as argument to something". Which means that:

($ 4) func

is the same as:

func $ 4

So:

map ($ 4) [odd, even]

means:

[($ 4) odd, ($ 4) even]
[(odd $ 4), (even $ 4)]
[False, True]

Why (func $) is not necessary

You could argue that, just like you can do (/ 4) and (2 /) which respectively means "divide something by 4" and "divide 2 by something", you could do ($ 4) and (func $) and you would be right.

In fact:

(func $) 4

is the same as:

func $ 4
func 4

which is the same as:

($ 4) func

But the reality is that:

map (func $) [...]

would be unnecessary, since the first argument of map is always applied to each argument to the list, making the above the same as:

map func [...]
3
bheklilr On
  1. $ 4 odd: This won't work because operators must be surrounded by parentheses when not used in infix form. If you were to do ($) 4 odd, this wouldn't work because argument order is incorrect, you want 4 to be the second argument. You could write ($) odd 4 though.

  2. ($ 4) odd: This does work because it's using operator sections, and here the 4 is provided as the second argument to $. It's like (++ "world") "hello " being the same as "hello " ++ "world".

  3. When you have ($ 4) :: Num a => (a -> b) -> b, and odd :: Integral a => a -> Bool, you just need to line up the types. Since every Integral a is also a Num a, we can just "upgrade" (constrain) the Num to Integral for this to work:

($ 4) :: Integral a => (a ->    b) -> b
odd   :: Integral a =>  a -> Bool

So a ~ a and b ~ Bool, so you can say that

($ 4) :: Integral a => (a -> Bool) -> Bool

So applying it to odd gives us

($ 4) odd :: Bool

This is because ($ 4) odd is the same as odd $ 4. Looking at the definition of $:

f $ x = f x

We can say that

odd $ 4 = odd 4

Which evaluates to False.

0
chi On

Infix operators like *, ++, or $ typically take two arguments as in

x ++ y

When one argument is missing, and they are put between parentheses, they instead form a section:

(x ++)
(++ y)

These sections are equivalent to, respectively,

\y -> x ++ y
\x -> x ++ y

i.e., they stand for the function that maps the "missing argument" to the result. For instance,

map ("A"++) ["a","b"] == [ "Aa","Ab" ]
map (++"A") ["a","b"] == [ "aA","bA" ]

Operator $ is not special in this respect. We have

(f $)
($ x)

which stands for

\x -> f $ x
\f -> f $ x

The first is not very useful, since (f $) is \x -> f $ x which is (eta-)equivalent to just f (*). The second is instead useful.

(*) To be picky, seq can distinguish between undefined and (undefined $), but this is a minor difference in practice.