简体   繁体   中英

Functors don’t work with data types that require specific types

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM