This works fine:
data Foo a = Foo a
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
This throws an Error:
data Foo = Foo String
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
Error:
Kind mis-match The first argument of `Functor' should have kind `* -> *', but `Foo' has kind `*' In the instance declaration for `Functor Foo'
What am I missing here? Why can't I use functors to wrap and unwrap Foo
if it holds a specific type?
UPDATE
I guess I can ask this another way:
data Foo = Foo String deriving(Show)
let jack = Foo "Jack"
-- Some functory thingy here
putStrLn $ show $ tail <$> jack
-- Foo "ack"
Why can't I do this? Or is there another construct for this use case?
That's because Foo
needs a single type variable to operate on.
The type of fmap
is:
fmap :: Functor f => (a -> b) -> f a -> f b
Now try to specialize this for your Foo
:
(a -> b) -> Foo -> Foo
Can you see where the problem is ? The types won't just match. So, to make Foo
a functor, it has to be something like this:
Foo a
so that when you specialize it for fmap it has the following proper type:
(a -> b) -> Foo a -> Foo b
Coming from a dynamic language you probably see a Functor as a container of stuff and fmap
as a way to transform things inside the container. However in Category theory a Functor can be seen as a way to transform a type into another type, and a way to transform a function on those type to function on the other type.
Imagine you have 2 differents worlds, one which is earth, and a virtual one when every body/things on earth has an avatar. The Functor is not the avatar but the magic wand which transforms everything to its avatar but also every single function of the real world into a function in the avatar world.
For example, with my magic wand I can transform a human to a frog (or a String to a list of Strings) but I can also transform the function "change the human hat" to change the frog hat" (or capitaize a String to capitalize all the String within a list).
fmap
is the way you transform a function to another : you can see it as
a function which take 2 arguments - a function and a container - and apply this function to each element of this container
but also as a function which take 1 argmunt - a function - an return a function which takes a container and return a container.
The way you create a type from a type is less obvious In your first example you probably just see Foo String
as new type, but you can also see Foo
as a super function whic take the type String
and return a new type : Foo String
. That's what the * -> * kind
is. Foo is not a type but a super function creating type from a type.
In your second example, Foo
is not a type creator but just a simple type (kind : *
), therefore it doesn't make sense to declare it as a functor.
If you really want to define fmap
for the plain Foo
in your 2nd example is to define a real functor and create a type alias for the plain type
data FooFunctor a = FooFunctor a
instance Functor Foofunctor where
fmap f (FooFunctor a) = FooFunctor (f a)
type Foo = FooFunctor String
The type of fmap
is generic; you can't constrain it:
fmap :: Functor f => (a -> b) -> f a -> f b
Those a
s and b
s must be completely polymorphic (within the constraints of your Functor
instance), or you don't have a Functor
. The handwavy way of explaining why this is is because a Functor
must obey some theoretical laws to make them play nice with Haskell's other data types:
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
If you have a data type that is parameterized over multiple types, ie:
data Bar a b = Bar a b
You can write a Functor
instance for Bar a
:
instance Functor (Bar a) where
fmap f (Bar a b) = Bar a (f b)
You can also write a Bifunctor
instance for Bar
:
instance Bifunctor Foo where
first f (Bar a b) = Bar (f a) b
second f (Bar a b) = Bar a (f b)
...which again must follow some laws (listed on the linked page).
Edit:
You could write your own class to handle the type of behavior you're looking for, but it would look like this:
class FooFunctor f where
ffmap :: (String -> String) -> f -> f
But in this case, we'd have to make new entire classes for every single permutation of "inner types" we might have (like String), in order to cover all bases.
You can also write a class (call it Endo
) that only permits endomorphisms (functions of type a -> a
) on the "inner type" of a data type, like this:
class Endo f where
emap :: (a -> a) -> f a -> f a
Then, if you changed your data type a bit, and instantiated an appropriate instance of Endo, eg
data Foo' a = Foo' a
type Foo = Foo' String
instance Endo Foo' where
emap f (Foo a) = Foo (f a)
...if you write functions of type Foo -> Foo
, you're guaranteed to preserve the "Stringiness" of the inner type you're mapping if you use emap
. A quick search on hayoo reveals that this type of thing is relatively common practice, but doesn't really exist as a standard type class.
A class that does pretty much exactly what you're asking for is MonoFunctor .
type instance Element Foo = String
instance MonoFunctor Foo where
fmap f (Foo s) = Foo (f s)
head "Jack"
is not the string "J"
, but the character 'J'
. So your own example shows why this does not work; head <$> jack
would have to give Foo 'J'
, which is not a valid value of type Foo
, since Foo
can only be applied to String
values, not Char
values.
"Some other construct" for this use case is to define a "map" function for Foo
, exactly as you're trying to define fmap. But that map function is not fmap, since it has to have type (String -> String) -> Foo -> Foo
. So there's no need (or possibility) to make Foo
an instance of Functor
and name your mapping function fmap
; the mapping function you want to use is simply not fmap.
Note that this means you cannot map arbitrary functions over your Foo
values; only functions which take and return strings (so head
is still out). Nor can you pass Foo
values to generic functions that accept values in any functor; those functions might try to fmap
functions over Foo
that do not return strings; they're allowed to do this because they specified that they need functors , and that's exactly what defines a functor.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.