Going through Typeclassopedia to gain some routing working with type classes. Want to make an alternative to Either
an instance of Functor
, but even examining the definition of Either
as an instance of Functor
keeps getting me in trouble.
Have this, but will not compile.
data Alt a b = Success a | Failure b deriving (Show, Eq, Ord)
instance Functor (Alt a) where
fmap _ (Failure a) = Failure a
fmap f (Success x) = Success (f x)
• Couldn't match expected type ‘a1’ with actual type ‘a’
‘a1’ is a rigid type variable bound by
the type signature for:
fmap :: forall a1 b. (a1 -> b) -> Alt a a1 -> Alt a b
at Brenty_tcop.hs:25:3-6
‘a’ is a rigid type variable bound by
the instance declaration
at Brenty_tcop.hs:24:10-24
• In the first argument of ‘f’, namely ‘x’
In the first argument of ‘Success’, namely ‘(f x)’
In the expression: Success (f x)
• Relevant bindings include
x :: a (bound at Brenty_tcop.hs:26:19)
f :: a1 -> b (bound at Brenty_tcop.hs:26:8)
fmap :: (a1 -> b) -> Alt a a1 -> Alt a b
(bound at Brenty_tcop.hs:25:3)
|
26 | fmap f (Success x) = Success (f x)
As @chepner says in the comments, your code will compile if you switch the order of the type parameters,
or alternatively switch the meaning of the
Functor
instance, so that it maps overFailure
and leavesSuccess
alone.Basically, the
Functor
type class only knows how to map over a type's last type parameter. So we had to rejig things so that we apply the functionf
to an occurrence of that last type parameter.Why you can only map over the rightmost parameter is a very deep and interesting question. To understand this you have to understand kinds, which are an advanced feature of Haskell's type system.
You can think of kinds as being the "next level" of types, in some sense. Types classify values; kinds classify types. So
"foo"
is aString
, andString
is a type. In Haskell "type" is pronounced*
.All ordinary types - those which can have values - have a kind of
*
. SoString :: *
,Int :: *
,Bool :: *
, etc.Things get interesting when you start thinking about parameterised types.
Maybe
is not a type by itself - you can't have values of typeMaybe
, but you can haveMaybe Int
,Maybe String
, etc. SoMaybe
is a sort of function - it takes a type as an argument and it produces a type. (Maybe
is a type constructor, to use the technical term.)Alt
is a two-parameter type function. Type functions are curried in Haskell, just like regular value functions, soAlt
has a type of* -> * -> *
(which really means* -> (* -> *)
).Now,
Functor
is a higher-order type function. It takes an argumentf
, which itself is a type function.Functor
on its own is not a valid type class constraint, butFunctor f
is.This means
Maybe
on its own, with a kind of* -> *
, is a valid argument for theFunctor
type function. ButInt :: *
isn’t, and nor isMaybe Int :: *
, and nor isAlt :: * -> * -> *
. The error messages tell you about the kind mismatch:The kind system is there to prevent you from forming invalid types, just like how the type system prevents you from writing invalid values. If there was no kind system, and we were allowed to write
instance Functor Alt
, it would produce the following (nonsensical) type forfmap
:So we need to turn
Alt :: * -> * -> *
into something of kind* -> *
, in order to have a valid argument forFunctor
.Alt
is a curried type function, so if we give it a single type argument, we'll get a type function back!That's why the
instance
declaration saysinstance Functor (Alt x)
- it needs to giveAlt
an argument (and in this case the argument can be any typex
as long as its kind is*
). Now we havefmap :: (a -> b) -> Alt x a -> Alt x b
, which is a valid type expression.So in general, the recipe for making a
Functor
instance is to start by giving arguments to your type until it only has one parameter left. That's whyFunctor
only knows how to map over the rightmost type parameter. As an exercise you can try defining aFunctor
class which maps over the second-to-last type parameter.This is a big topic so hopefully I haven't gone too fast. It's OK not to understand kinds straight away - it took me several tries! Let me know in the comments if there's anything you'd like me to explain further.