$ (apply) is not always a valid substitute for parens

I’ve gotten into the habit of using $ instead of parenthesis whenever possible, but I sometimes encounter unexpected compilation failures with this approach. Is this a bug, or am I misunderstanding how the type system works?

type Foo = forall a. a -> a
type Bar = forall a. Eq a => a -> a

type Stuff =
  { foo :: Foo
  , bar :: Bar

broken :: Stuff -> Int
broken r =
  r.bar $ r.foo 1

workaround :: Stuff -> Int
workaround r =
  r.bar (r.foo 1)
onlyAffectsRecords :: Foo -> Bar -> Int
onlyAffectsRecords foo bar =
  bar $ foo 1


I don’t know why this issue only occurs when records are involved. It could be similar to this other bug; however, that involves record destructuring, while this example does not.

This is due to impredicativity, and there’s quite a bit of literature around this. It’s the same reason you can’t use $ with ST.run. GHC is getting a neat feature in 9.2 called “Quick-Look Impredicativity” if you want to check it out.

The gist of it is that $ is not going to preserve polytypes (foralls, constraints) with our existing type-checker rules. It requires instantiating a type variable with another polytype, which we do not do.

Oh, I take that back. I should have read more clearly (gotta stop replying to quickly!). I’m not sure why it wouldn’t work with a record in this case, if it works with individual arguments.

My guess would be that there’s a subtle difference in the typechecker between looking up a variable directly in the environment vs inferring the type of record projection.