import Control.Applicative
main = print $ fmap (*2) (1,2)
produces (1,4)
. I would expect it it to produce (2,4)
but inst
The Functor
instance is actually from the GHC.Base module which is imported by Control.Applicative
.
Trying to write the instance I want, I can see that it won't work, given the definition of tuples; the instance requires just one type parameter, while the 2-tuple has two.
A valid Functor
instance would at least have to be on tuples, (a,a)
that have the same type for each element, but you cannot do anything sneaky, like define the instance on:
type T2 a = (a,a)
because instance types aren't permitted to be synonyms.
The above restricted 2-tuple synonym is logically the same as the type:
data T2 a = T2 a a
which can have a Functor instance:
instance Functor T2 where
fmap f (T2 x y) = T2 (f x) (f y)
As Gabriel remarked in the comments, this can be useful for branching structures or concurrency.
Let me answer this with a question: Which output do you expect for:
main = print $ fmap (*2) ("funny",2)
You can have something as you want (using data Pair a = Pair a a
or so), but as (,)
may have different types in their first and second argument, you are out of luck.
Pairs are, essentially, defined like this:
data (,) a b = (,) a b
The Functor
class looks like this:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Since the types of function arguments and results must have kind *
(i.e. they represent values rather than type functions that can be applied further or more exotic things), we must have a :: *
, b :: *
, and, most importantly for our purposes, f :: * -> *
. Since (,)
has kind * -> * -> *
, it must be applied to a type of kind *
to obtain a type suitable to be a Functor
. Thus
instance Functor ((,) x) where
-- fmap :: (a -> b) -> (x,a) -> (x,b)
So there's actually no way to write a Functor
instance doing anything else.
One useful class that offers more ways to work with pairs is Bifunctor
, from Data.Bifunctor
.
class Bifunctor f where
bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
bimap f g = first f . second g
first :: (a -> b) -> f a y -> f b y
first f = bimap f id
second :: (c -> d) -> f x c -> f x d
second g = bimap id g
This lets you write things like the following (from Data.Bifunctor.Join
):
newtype Join p a =
Join { runJoin :: p a a }
instance Bifunctor p => Functor (Join p) where
fmap f = Join . bimap f f . runJoin
Join (,)
is then essentially the same as Pair
, where
data Pair a = Pair a a
Of course, you can also just use the Bifunctor
instance to work with pairs directly.