I have following type definition:
newtype Flip f a b =
Flip (f b a) deriving (Eq, Show)
Does the Flip
data constructor has one or three arguments?
Consinder following implementation:
data K a b = K a
newtype Flip f a b =
Flip (f b a) deriving (Eq, Show)
instance Functor (Flip K a) where
fmap f (Flip (K b)) = Flip (K (f b))
What is the type of (Flip K a)
?
The Flip
data constructor has one argument. That argument has type f b a
.
That thus means that f
itself is a higher order type argument with type f :: * -> * -> *
. A more rigorous newtype
statement would be:
newtype Flip (f :: * -> * -> *) a b = Flip (f b a)
You can thus for instance instantiate a Flip Either Int Bool
, since Either
is a type that requires two additional type parameters, and then construct a Flip (Right 1) :: Flip Either Int Bool
.
What is the type of
(Flip K a)
?
Flip K a
is not a fully applied type. In pseudo-code, it has type b -> Flip K a b
. Once the b
has been resolved (Functor
works with higher order types), we know that the only argument of Flip
will have a K b
constructor. So for instance Flip (K 1)
is a Flip K a Int
type.
The future is now, when you (use ghc 8 and) switch on a flag or two
Prelude> :set -XPolyKinds -XFlexibleInstances
Let us declare
Prelude> newtype Flip f a b = MkFlip (f b a)
and then enquire
Prelude> :kind Flip
Flip :: (k1 -> k -> *) -> k -> k1 -> *
Prelude> :type MkFlip
MkFlip
:: forall k k1 (b :: k) (f :: k -> k1 -> *) (a :: k1).
f b a -> Flip f a b
The type constructor Flip
takes two implicit arguments, being k
and k1
, and three explicit arguments, being a binary function producing a type, then its two arguments in reverse order. The arguments to this function are of unconstrained type (old people can say "kind" if they like), but it certainly returns a type (in the strict sense of "thing in *
", rather than the uselessly vague sense of "any old rubbish right of ::
") because it is certainly used as a type in the declaration of MkFlip
.
The data constructor, MkFlip
, takes five implicit arguments (exactly the arguments of Flip
) and one explicit argument, being some data in f b a
.
What's going on is Hindley-Milner type inference one level up. Constraints are collected (e.g., f b a
must inhabit *
because a constructor argument must inhabit f b a
) but otherwise a most general type is delivered: a
and b
could be anything, so their types are generalised as k1
and k
.
Let's play the same game with the constant type constructor:
Prelude> newtype K a b = MkK a
Prelude> :kind K
K :: * -> k -> *
Prelude> :type MkK
MkK :: forall k (b :: k) a. a -> K a b
We see that a :: *
but b
can be any old rubbish (and for that matter, k :: *
, as these days, * :: *
). Clearly, a
is actually used as the type of a thing, but b
is not used at all, hence unconstrained.
We may then declare
Prelude> instance Functor (Flip K b) where fmap f (MkFlip (MkK a)) = MkFlip (MkK (f a))
and ask
Prelude> :info Flip
...
instance [safe] forall k (b :: k). Functor (Flip K b)
which tells us that the unused b
can still be any old rubbish. Because we had
K :: * -> k -> *
Flip :: (k1 -> k -> *) -> k -> k1 -> *
we can unify k1 = *
and get
Flip K :: k -> * -> *
and so
Flip K b :: * -> *
for any old b
. A Functor
instance is thus plausible, and indeed deliverable, with the function acting on the packed up a
element, corresponding to the argument of Flip K b
which becomes the first argument of K
, hence the type of the stored element.
Unification-based type inference is alive and (fairly) well, right of ::
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With