haskellsyntaxoperatorsdollar-signhaskell-prelude

What actually $ function does in haskell?


I know

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

Intuitively it seems to me, like to say, 1. $ delays the evaluation of the function to its left 2. evaluates whats to its right 3. feeds the result of its left to its right.

And it makes perfect sense to me when,

ghci> length $ [1..5]
5
ghci> ($) length [1..5]
5

What I do not understand is why,

ghci> ($ [1..5]) length
5

Judging from the type of $, isn't that its (first) argument should be a function ?


Solution

  • This has to do with parsing. In Haskell you can write (op arg) where op is an infix operator. This is not the same as ((op) arg). And you can write (arg op) as well! For example:

    GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
    Prelude> :t (+ 4)
    (+ 4) :: Num a => a -> a
    Prelude> :t (4 +)
    (4 +) :: Num a => a -> a
    

    That is, (+ 4) is the function \x -> x + 4 and (4 +) is the function \y -> 4 + y. In the case of addition these are equal functions, but that is not really important right now.

    Now let us try the same trick on $:

    Prelude> :t ($ [1,2,3,4])
    ($ [1,2,3,4]) :: Num t => ([t] -> b) -> b
    

    Now surprise so far, we got \f -> f $ [1,2,3,4]. We can also write

    Prelude> :t (length $)
    (length $) :: [a] -> Int
    

    to get the function \l -> length $ l. But how about this:

    Prelude> :t ($ length)
    ($ length) :: (([a] -> Int) -> b) -> b
    

    This is strange, but it makes sense! We got \f -> f $ length, i.e., a functional which expects to get a function f of type ([a] -> Int) -> b) that will be applied to length. There is a fourth possibility:

    Prelude> :t ([1,2,3,4] $)
    
    <interactive>:1:2:
        Couldn't match expected type `a0 -> b0' with actual type `[t0]'
        In the first argument of `($)', namely `[1, 2, 3, 4]'
        In the expression: ([1, 2, 3, 4] $)
    

    Everything is as it should be because [1,2,3,4] is not a function. What if we write $ in parenthesis? Then its special meaning as an infix operator disappears:

    Prelude> :t (($) length)
    (($) length) :: [a] -> Int
    
    Prelude> :t (($) [1,2,3,4])
    <interactive>:1:6:
        Couldn't match expected type `a0 -> b0' with actual type `[t0]'
        In the first argument of `($)', namely `[1, 2, 3, 4]'
        In the expression: (($) [1, 2, 3, 4])
    
    Prelude> :t (length ($))
    <interactive>:1:9:
        Couldn't match expected type `[a0]'
                    with actual type `(a1 -> b0) -> a1 -> b0'
        In the first argument of `length', namely `($)'
        In the expression: (length ($))
    
    Prelude> :t ([1,2,3,4] ($))
    <interactive>:1:2:
        The function `[1, 2, 3, 4]' is applied to one argument,
        but its type `[t0]' has none
        In the expression: ([1, 2, 3, 4] ($))
    

    So, to answer your question: $ [1,2,3,4] is parsed as \f -> f $ [1,2,3,4] so it makes perfect sense to apply it to length. However ($) [1, 2, 3, 4] does not make much sense because ($) is not seen as an infix operator.

    By the way, $ does "not do anything", so to speak. It is mostly used for more readable input because it has low precedence and so we can write f $ g $ h $ x instead of f (g (h x)).