Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why dollar ($) operator is so complex in GHC 8.0.1?

Tags:

Prelude> :i ($) ($) ::   forall (r :: GHC.Types.RuntimeRep) a (b :: TYPE r).   (a -> b) -> a -> b         -- Defined in ‘GHC.Base’ infixr 0 $ 

How is it different from (a -> b) -> a -> b? Is there any b that does not fit the new type signature?

like image 753
sevo Avatar asked Aug 27 '16 22:08

sevo


1 Answers

Before 8.0, there was a special case in the typechecker to make applications of $ to unlifted kinds work. This also meant that you couldn't define your own functions which could work with both lifted and unlifted kinds. Now that this so-called Levity Polymorphsim ('levity' refers to 'the degree to which something is lifted' - or 'lifted-ness', because of 'unlifted' and 'lifted' types) is built into the typechecker, this is possible:

import GHC.Exts (TYPE, RuntimeRep(..))  import Data.Kind (type (*))  ap :: forall (a :: *) (b :: *) . (a -> b) -> (a -> b)  ap f x = f x   ap_LP :: forall (a :: *) (b :: TYPE r) . (a -> b) -> (a -> b)  ap_LP f x = f x 

and indeed the $ function is now defined identically to ap_LP, with no special case needed in the typechecker to make $ work with functions returning unlifted types (there is a still a special case in the typechecker to make polymorphic application, i.e. runST $ ... work, but this is unrelated to levity polymorphism). This is essentially the reason for the added complexity - there are fewer 'hacks' in the type system now, and users of GHC can take advantage of levity polymorphism just by giving a function the appropriate type (note that levity-polymorphic types are never inferred, as far as I can tell). Before levity polymorphism, if you wanted to write a polymorphic function which could possible work on both lifted and unlifted kinds, you were obligated to write two identical copies of the function with different type signatures.

The new type differs from the old one in that the new type is strictly more general than the old one:

-- ok  ap' :: forall (a :: *) (b :: *) . (a -> b) -> (a -> b)  ap' = ap_LP   -- type error:  --    * Couldn't match a lifted type with an unlifted type ap_LP' :: forall (a :: *) (b :: TYPE r) . (a -> b) -> (a -> b)  ap_LP' = ap  

In other words, every b which 'fit' the old signature must (by definition) fit the new type signature (and so this change is perfectly backwards-compatible!).


Also note that the following is not possible:

ap'' :: forall (a :: TYPE r) (b :: *) . (a -> b) -> (a -> b) ap'' f x = f x  

The error produced is

A representation-polymorphic type is not allowed here:   Type: a   Kind: TYPE r In the type of binder `x' 

and SPJ explains the reason for the restriction here:

It's absolutely right that the second argument to ($) must not have an unboxed kind. Because the code for ($) must move that argument around (pass to the function), so it must know its width, pointerhood ect.

But actually it would be ok for the result of the call (f $ x) to be unboxed, because the code for ($) doesn't mess with the result; it just tail-calls f.

This is to say that not every levity-polymorphic type has a valid inhabitant - and this relates to the operational distinction between unboxed and boxed types, which can only be treated uniformly in certain cases, and the typechecker makes sure of it.

like image 103
user2407038 Avatar answered Nov 17 '22 01:11

user2407038