Difference between `(+ 1) $ (+ 2)` and `(+ 1) (+ 2)`

Currently I’m studying function application with $, but I don’t really understand why the following happens :
I try to explore the difference between (+ 1) $ (+ 2) and (+ 1) (+ 2).
I first check their types :

(+ 1) (+ 2) :: (Num a, Num (a -> a)) => a -> a
(+ 1) $ (+ 2) :: (Num a, Num (a -> a)) => a -> a

It’s not suprise that they have the same type because of the definition of $.

The weird thing happens when I try to evaluate (+ 1) $ (+ 2) and (+ 1) (+ 2) at 1.

ghci > (+ 1) $ (+ 2) 1 
4

But (+ 1) (+ 2) 1 doesn’t work, error message says “No instance for Num (Integer -> Integer)” and of course (+ 1) ((+ 2) 1) will fix this problem.

I’m confused because the two function has exactly the same type and same type constraints. Why (+ 1) $ (+ 2) notices the instance and (+ 1) (+ 2) doesn’t.

2 Likes

Function application binds to the left. So your (+1) (+2) 1 reads as ((+1) (+2)) 1 amd GHC is trying to apply (+1) to the function (+2) hence the error message that function type Integer -> Integer has no Num instance.

$ on the other hand has wery low priority, allowing (+2) 1 to bind naturally, that’s it’s probably most common usage scenario.

5 Likes

I noticed the fact you said, but I think Haskell shouldn’t be able to check the type of (+ 1) (+ 2). Since function application binds to left when (+ 1) takes (+ 2) I think Haskell should somehow “Get stuck”. I’m really interested in how does Haskell pass it and produce the type.

1 Like

It’s able to check the type, but weirdly. Since plain (+1) has a type of Num a => a -> a, when it sees (+1) (+2) it tells you ‘it’ll work if you provide an instance Num (a -> a)’ (which you don’t).

I guess some of the confusion actually comes from the type of (+1) $ (+2) which is what GHC shows, but isn’t what it uses when you give (+1) $ (+2) 1.

Try running ((+1) $ (+2)) 1 or let f = (+1) $ (+2) in f 1 and note the error message is as in your first case.

4 Likes

Thank you very much! I got it.

Thank you for clarifying. Admittedly this one made my gears spin. So IIUC GHCI does some amout of rewriting before inferring types?

GHCi does nothing special here. It’s just that (+1) $ (+2) is not a subexpression of (+1) $ (+2) 1 because of the associativity. Instead, it is a subexpression of let f = (+1) $ (+2) in f 1 or equivalently ((+1) $ (+2)) 1. The bracketing is important.

11 Likes