Defining your own Functor

I've been trying to understand what a Functor is in Haskell, and for that I've want an example of a Functor, without any other properties.

The working example I came up with was

data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
  fmap g (MyFunctor a b) = MyFunctor a $ g (b)

which I guess is a semi-practical Functor as the left value can be stored safely while the right value operated on. I then want to have the left value be replaced with the right value before the operation. So that...

Main> fmap (+2) MyFunctor 2 5
MyFunctor 5 7

Changing the instance declaration to do that however, yields an error.

data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
  fmap g (MyFunctor a b) = MyFunctor b $ g (b)

Which I don't understand, or know what to search to find a similar enough question to help me.

C:\Haskell\func.hs:3:28: error:
    * Couldn't match type `a1' with `a'
      `a1' is a rigid type variable bound by
        the type signature for:
          fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
        at C:\Haskell\func.hs:3:3
      `a' is a rigid type variable bound by
        the instance declaration at C:\Haskell\func.hs:2:10
      Expected type: MyFunctor a b
        Actual type: MyFunctor a1 b
    * In the expression: MyFunctor b $ g (b)
      In an equation for `fmap':
          fmap g (MyFunctor a b) = MyFunctor b $ g (b)
      In the instance declaration for `Functor (MyFunctor a)'
    * Relevant bindings include
        b :: a1 (bound at C:\Haskell\func.hs:3:23)
        a :: a (bound at C:\Haskell\func.hs:3:21)
        g :: a1 -> b (bound at C:\Haskell\func.hs:3:8)
        fmap :: (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
          (bound at C:\Haskell\func.hs:3:3)

Upvotes: 2

Views: 1120

Answers (4)

Lamdas Everywhere
Lamdas Everywhere

Reputation: 1686

First up, it's really good you're trying to understand this. It's one of the underpinnings of all the amazing things in Haskell.

Second up, I think there are better ways to understand Functor than trying to create an instance yourself. One of the best ways is to first up use fmap on as many existing Functor instances are you can find. There are already a bunch in the Prelude. Look at the source code of them to see how they're instantiated. This will give you an intuition for what they are.

The type you've "created" above is actually already in the prelude, for example. It's called (,). (Made a mistake of saying this was Either earlier - it's not, as that type is data Either a b = Left a | Right b sorry about that.)

What you want to do is get a feeling for the roughly ten Functors that are in Prelude first. But don't just get a feeling for the Functor part. Make sure you understand the underlying data type and structure. For this reason, this is going to be a bit of a futile exercise unless you truly understand types, algebraic data types (both sum and product), multi-parameter types and typeclasses (not how to use or instantiate typeclasses, just what they are, and how the mechanism works).

If you want a good intuitive introducion to using the basics of these things, I'd say work through the book I helped work on to do this: http://happylearnhaskelltutorial.com)

There is a big difference between using Functor instances, and creating them. I think you'll get a much better intuition of what they mean and are if you use a bunch of them. Only then, IMHO, should you approach instantiating one yourself. At that point, also, it's a good idea to look up the laws, because they matter. This should clear up any misconceptions you have of what they are or what they could be.

To put you on the path, though, they are about applying a function within the most shallow level of some structure. That structure is the structure of the type in question. So, in a List, it'll be all the "values within the list" (for want of a better way to describe lists), because if you look at the defined type of List, you'll see that that's actually the most shallow level of a list. To understand why, though, you need to really understand the type of lists in Haskell.

So, I would check out how the following types work as Functors: Maybe, List (aka []), Either, Data.Map, (->), (,). Data.Tree (rose trees), the Identity type in Control.Monad.Identity (just about the simplest parameterized algebraic data type you can have, it wraps its value: data Identity a = Identity a). Do a search for more (there are heaps!)

I wish you a happy time!

Upvotes: 2

amalloy
amalloy

Reputation: 91857

I then want to have the left value be replace with the right value before the operation.

This is an illegal thing for a Functor to do: GHC is correctly telling you that the types don't work out. In particular, the left part of your MyFunctor value may have a different type than the right part, as in MyFunctor 5 "hello". And since fmap must operate on only the last type parameter of your type, it must therefore leave the first part alone. Let's look at a more specific example.

Recall that the type of fmap is

fmap :: Functor f => (a -> b) -> f a -> f b

And suppose you have an object you'd like to fmap over:

obj :: MyFunctor Int String

What, then, must be the type of f to call fmap f obj? To unify the involved types, we must have

f :: (String -> a)

and

fmap f obj :: MyFunctor Int a

So you can see that it is impossible to "replace" the first, Int, field with the previous value of the second, String field: the only thing allowed in that place is what was there before, an Int!

Now, you might imagine that you could make the types work out if you changed your MyFunctor definition so that the two fields have the same type:

data MyFunctor a = MyFunctor a a

-- Also illegal
instance Functor MyFunctor where
  fmap f (MyFunctor a b) = MyFunctor b (f b)

But you can't do this either, because b and f b may be of different types, and the caller gets to choose what f to use, so your implementation of fmap may not assume they are the same.

In fact, for any given data type, there is at most one legal definition of Functor: if you find one which is legal, you can be certain that any other definition is illegal: either the types won't match up, or it will break one of the two Functor laws:

fmap id == id
fmap f . fmap g == fmap (f . g)

How might you break one of these laws while still having the types line up? By operating on some piece of the structure that is not part of the structure being fmaped over. For example, often someone writes a (bad!) Functor like this:

data Counter a = Counter Int a

instance Functor Counter where
  fmap f (Counter n x) = Counter (n + 1) (f x)

But this breaks both of the Functor laws, because it allows you to count how many times fmap has been called, which is supposed to be a detail which is not exposed.

fmap id (Counter 0 0) == Counter 1 0
(fmap tail . fmap tail) /= fmap (tail . tail)

Upvotes: 5

Mark Seemann
Mark Seemann

Reputation: 233125

I don't think you can swap the values around like that. You've declared MyFunctor to contain two values of the generic types a and b. These are not necessarily of the same type. You could, for example, create a value like this:

MyFunctor "foo" 42

When you declare an instance of Functor (MyFunctor a), you've basically said that for any generic type a, you can map MyFunctor a b to MyFunctor a c.

For example, if a is String, as in the above example, you can map MyFunctor String b to MyFunctor String c if you have a function b -> c.

You can't swap the arguments, because the first value must retain its type.

It can't do that in the above example if c is any other type than String.

Upvotes: 0

chi
chi

Reputation: 116139

The error message is quite verbose, and tells the whole story.

First, it tells us that the type of fmap, in that instance should be

fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b

Hence, if f is any function a1 -> b, we must make fmap f to have type MyFunctor a a1 -> MyFunctor a b.

Note how the return type is MyFunctor a b. Indeed, that's what GHC expected:

Expected type: MyFunctor a b

But instead it found something else:

Actual type: MyFunctor a1 b

How could that be? Well, fmap returns MyFunctor b (g b) and g b has type b (correct, as expected), but b (the value) has type a1, instead of the expected a.

More practically, fmap can only alter the b component in MyFunctor a b, it can not affect the a component. This can be seen from the type signature of fmap.

Upvotes: 1

Related Questions