Reputation: 434
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
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 Functor
s 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 Functor
s: 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
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 fmap
ed 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
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
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