David Feuer
2017-12-28 02:16:27 UTC
Currently, we have something like
($) :: forall r1 r2 (a :: TYPE r1) (b :: TYPE r2).
(a -> b) -> a -> b
f $ x = f x
And that's only part of the story: GHC has a hack in the type checker to
give ($) an impredicative type when fully applied. This allows it to be
used when its function argument requires a polymorphic argument.
This whole complicated situation could be resolved in a very simple manner:
change the type and definition thus.
($) :: a -> a
($) f = f
All the type complications go away altogether, and ($) becomes plain
Haskell 98.
There are only three potential downsides I can think of:
1. The optimizer will see `($) x` as fully applied, which could change its
behavior in some cases. There might be circumstances where that is bad. I
doubt there will be many.
2. The new type signature may obscure the purpose of the operator to
beginners. But based on my experience on StackOverflow, it seems beginners
tend to struggle with the idea of ($) anyway; this may not make it much
worse. I suspect good Haddocks will help alleviate this concern.
3. Some type family and class instances may not be resolved under certain
circumstances which I suspect occur very rarely in practice.
class C a where
m :: (a -> a) -> ()
instance C (a -> b) where
m _ = ()
test :: ()
test = m ($)
Today, this compiles with no difficulties; with the proposed change, the
user would have to supply a type signature to make it work:
test = m (($) :: (a -> b) -> (a -> b))
This can also change when an INCOHERENT instance is selected under
similarly contrived circumstances, but those who use such generally deserve
what they get.
David
($) :: forall r1 r2 (a :: TYPE r1) (b :: TYPE r2).
(a -> b) -> a -> b
f $ x = f x
And that's only part of the story: GHC has a hack in the type checker to
give ($) an impredicative type when fully applied. This allows it to be
used when its function argument requires a polymorphic argument.
This whole complicated situation could be resolved in a very simple manner:
change the type and definition thus.
($) :: a -> a
($) f = f
All the type complications go away altogether, and ($) becomes plain
Haskell 98.
There are only three potential downsides I can think of:
1. The optimizer will see `($) x` as fully applied, which could change its
behavior in some cases. There might be circumstances where that is bad. I
doubt there will be many.
2. The new type signature may obscure the purpose of the operator to
beginners. But based on my experience on StackOverflow, it seems beginners
tend to struggle with the idea of ($) anyway; this may not make it much
worse. I suspect good Haddocks will help alleviate this concern.
3. Some type family and class instances may not be resolved under certain
circumstances which I suspect occur very rarely in practice.
class C a where
m :: (a -> a) -> ()
instance C (a -> b) where
m _ = ()
test :: ()
test = m ($)
Today, this compiles with no difficulties; with the proposed change, the
user would have to supply a type signature to make it work:
test = m (($) :: (a -> b) -> (a -> b))
This can also change when an INCOHERENT instance is selected under
similarly contrived circumstances, but those who use such generally deserve
what they get.
David