0

There are different ways of function application in Haskell, but it seems that each has its specificities. I'm trying to understand the differences between $, . and ():

Prelude> 100 / fromIntegral( length [1..10] )
10.0
Prelude> 100 / fromIntegral . length  [1..10]

<interactive>:193:22: error:
    * Couldn't match expected type `a -> Integer'
                  with actual type `Int'
    * Possible cause: `length' is applied to too many arguments
      In the second argument of `(.)', namely `length [1 .. 10]'
      In the second argument of `(/)', namely
        `fromIntegral . length [1 .. 10]'
      In the expression: 100 / fromIntegral . length [1 .. 10]
    * Relevant bindings include
        it :: a -> c (bound at <interactive>:193:1)
Prelude> 100 / fromIntegral $ length [1..10]

<interactive>:194:1: error:
    * Non type-variable argument
        in the constraint: Fractional (Int -> b)
      (Use FlexibleContexts to permit this)
    * When checking the inferred type
        it :: forall b. (Num b, Fractional (Int -> b)) => b
Prelude>

Why is the behaviour different in this case, and only parentheses () work?

duplode
  • 33,731
  • 7
  • 79
  • 150
devio
  • 1,147
  • 5
  • 15
  • 41
  • `100 / (fromIntegral . length) [1..10]` works as well. Why do you expect the `.` to work? – Willem Van Onsem Mar 31 '18 at 09:58
  • FYI https://stackoverflow.com/questions/940382/what-is-the-difference-between-dot-and-dollar-sign (possible duplicate?) – Stéphane Laurent Mar 31 '18 at 10:13
  • @StéphaneLaurent Definitely a duplicate, though I must say I'm a tiny bit tempted to cheekily invert the direction of the duplicate -- closing against duplicate targets with lots and lots of answers feels a little... inconclusive. – duplode Mar 31 '18 at 13:11
  • @duplode I think it's not important to close (who cares? ;-)). But it's nice to link the duplicate. This question is more specific to a particular example. The "duplicate" addresses the more general question. – Stéphane Laurent Mar 31 '18 at 13:26
  • 1
    @StéphaneLaurent I like duplicate closing because I see it as a happy outcome. When it goes right, it saves the time of both answerers (who don't have to actually write) and askers (who don't have to wait for an answer to be written), highlights good answers to the target question and avoids unnecessary duplication of content, all of that while actually helping the asker. That said, you have a point in that the specific example with the type error adds an extra nuance to this question. I have addressed it by adding a second duplicate target. – duplode Mar 31 '18 at 13:40

2 Answers2

4

Only one of these is actually built-in function application syntax: the one you call (). (Note that it doesn't actually require any parentheses though, just juxtaposition.) All Haskell expressions ultimately boil down to this syntax; infix-operator style is just syntactic sugar for it. The examples you've tried desugar thus:

〖〗: 100 / fromIntegral( length [1..10] )
      ≡ (/) 100 (fromIntegral (length [1..10]))
〖〗: 100 / fromIntegral . length [1..10]
      ≡ (/) 100 ((.) fromIntegral (length [1..10]))
〖〗: 100 / fromIntegral $ length [1..10]
      ≡ ($) ((/) 100 fromIntegral) (length [1..10])

The reason they desugar so differently is infix precedence rules: every infix operator comes with a fixity declaration. The ones relevant here:

Prelude> :i .
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in ‘GHC.Base’
infixr 9 .
Prelude> :i $
($) ::
  forall (r :: GHC.Types.RuntimeRep) a (b :: TYPE r).
  (a -> b) -> a -> b
    -- Defined in ‘GHC.Base’
infixr 0 $
Prelude> :i /
class Num a => Fractional a where
  (/) :: a -> a -> a
  ...
    -- Defined in ‘GHC.Real’
infixl 7 /

What this tells us is: . binds more tightly than /, which binds more tightly than $. So, for example w . x / y $ z is parsed as ((w . x) / y) $ z, which is sugar for

         ($) ((/) ((.) w x) y) z

(Function application itself – the one you called ()always binds more tighly than any infix operator).

Ok, the diet expressions above look confusing, so let's go back to a sugared form again but still with explicit parenthesis-grouping:

〖〗  ≡ 100 / (fromIntegral (length [1..10]))
〖〗  ≡ 100 / (fromIntegral . (length [1..10]))
〖〗  ≡ (100 / fromIntegral) $ (length [1..10])

It should be immediately clear that 〖〗 can't be right, regardless of what the operators actually do: you're trying to divide a number by a function, that doesn't make sense! The low precedence of the $ operator is specifically intended to separate expressions – basically whenever you see this operator, you can just imagine that each of the operands has parentheses around it.

〖〗 looks sensible enough parsing-wise, however it does not make sense type-wise: . is not a function application operator but a function composition operator. I.e. both of its operands should be functions, but (length [1..10]) is already the result of applying a function to its (only) argument, so you're trying to compose a function with a number.

You could actually write this expression using the composition operator, but you'd have to compose the functions before applying either of them to an argument (and only apply the argument to the composition-chain):

〖′〗: 100 / (fromIntegral . length) [1..10]
       ≡ 100 / (\x -> fromIntegral (length x)) [1..10]
       ≡ 100 / fromIntegral (length [1..10])
       ≡ 〖〗

Now as for $, that is actually a function application operator, so you can use it too. Just, you need to make proper use of its low fixity, and make sure it does not interfere with the higher-precedence / operator:

〖′〗: 100 / (fromIntegral $ length [1..10])
        ≡ 100 / ((fromIntegral) (length [1..10]))
        ≡ 100 / fromIntegral (length [1..10])
        ≡ 〖〗
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
1

(.) is a function composition operator not function application. (.) is defined with precedence level 9 and function application is even higher. Therefore in your second example length [1..10] is evaluated first to 10 of type Int:

fromIntegral . length [1..10]

is reduced to:

fromIntegral . 10

But (.) has the type (b -> c) -> (a -> b) -> a -> c. I.e. the Int and the (a -> b) do not match.

mschmidt
  • 2,740
  • 4
  • 17
  • 31