Reputation: 2967
I've got the following data type:
data Users id height weight = User id height weight
instance Functor Users where
fmap f (User id height weight) = User(f id height weight)
Yet this won't compile?
It works fine when I use a type with a single parameter, such as:
data Users id = User id
instance Functor Users where
fmap f (User id) = User (f id)
Why isn't my first example working?
Upvotes: 1
Views: 1852
Reputation: 991
Instances of the Functor
class can be defined for types of first-order kind only, which are those of kind * -> *
If you try querying GHCI as follows
ghci> :kind Users
Users :: * -> * -> * -> *
you will understand that you custom type is of much higher kindness, hence the compiler error.
If you still wish to provide an instance of the Functor
class for it, despite how useful that could be, do as follows
ghci> :{
| -- define custom type having a too high kind
| data User a b c = User a b c deriving Show
|
| -- provide functor instance by reducing its type kindness
| instance Functor (User a b) where
| fmap f (User x y z) = User x y (f z)
| :}
The trick is to partially apply the User
type constructor so to produce a type having kindness reduced to the expected one. Then, define your fmap
by pattern matching the whole User
value constructor and apply the function f
to the last input value.
Example of usage is:
ghci> u1 = User 1 168 58.9
ghci> fmap (+5) u1
User 1 168 63.9
That's roughly how the Either
, tuple (,)
and function (->)
types, which are also higher-order kinded types in Haskell prelude, are able to provide instances of the Functor
class too.
Upvotes: 2
Reputation: 532053
Each type and type constructor has a kind. Something simple like Int
has kind *
. Your single-argument type constructor Users
has kind * -> *
; it takes one type and returns a new one. Your first example of Users
has kind * -> * -> * -> *
; it takes three types and returns a new one.
Functor
only works with type constructors of kind * -> *
. This allows you to define a Functor
instance for your second Users
type constructor, but not your first one.
Think about your first try: the data constructor Users
takes three arguments, but your definition of fmap
attempts to call it with just one, the return value of f. You could make Users
a functor as long as you are willing to make all three fields the same type:
data Users a = Users a a a
instance Functor Users where
fmap f (Users a b c) = Users (f a) (f b) (f c)
In theory, you could define a class Trifunctor
(there is a Bifunctor
class available):
class Trifunctor f where
trimap :: (a1 -> b1) -> (a2 -> b2) -> (a3 -> b3) -> f a1 a2 a3 -> f b1 b2 b3
data Users a b c = Users a b c
instance Trifunctor Users where
trimap f g h (Users a b c) = Users (f a) (g b) (h c)
but it's debatable how useful this would be. Functor
is useful for general purpose containers, because they have a wide range of uses. Users
, on the other hand, seems pretty specific. You generally don't need the flexibility you are defining it with; data User = User Int Int Int
seems like it would work fine, and there is no need to map a function over this: how often to you need the same function to modify height, weight, and age in the same way?
Upvotes: 7