Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manipulating the order of arguments to type constructors

I wrote something like this:

instance Functor (Either e) where

   fmap _ (Left a) = Left a

   fmap f (Right b) = Right (f b)

How do I do the same if I want fmap to change the value only if it's Left?

I mean, what syntax do I use to indicate that I use type Either _ b instead of Either a _?

like image 299
mik01aj Avatar asked Feb 25 '10 17:02

mik01aj


3 Answers

I don't think there's a way to do that directly, unfortunately. With a function you can use flip to partially apply the second argument, but that doesn't work with type constructors like Either.

The simplest thing is probably wrapping it in a newtype:

newtype Mirror b a = Mirrored (Either a b)

instance Functor (Mirror e) where
    fmap _ (Mirrored (Right a)) = Mirrored $ Right a
    fmap f (Mirrored (Left b)) = Mirrored $ Left (f b)

Wrapping with newtype is also the standard way to create multiple instances for a single type, such as Sum and Product being instances of Monoid for numeric types. Otherwise, you can only have one instance per type.

Additionally, depending on what it is you want to do, another option is to ignore Functor and define your own type class like this:

class Bifunctor f where
    bimap :: (a -> c) -> (b -> d) -> f a b -> f c d

instance Bifunctor Either where
    bimap f _ (Left a)  = Left  $ f a
    bimap _ g (Right b) = Right $ g b

instance Bifunctor (,) where
    bimap f g (a, b) = (f a, g b)

Obviously, that class is twice as much fun as a regular Functor. Of course, you can't make a Monad instance out of that very easily.

like image 184
C. A. McCann Avatar answered Nov 04 '22 12:11

C. A. McCann


You can't make the instance you are looking for directly.

In order for type inference and type classes to work, there is a certain positional bias to the ordering of arguments in the types. It has been shown that if we allowed arbitrary reordering of the arguments when instantiating type classes, that type inference becomes intractable.

You could use a Bifunctor class that can map over both arguments separately.

class Bifunctor f where
    bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
    first :: (a -> b) -> f a c -> f b c
    second :: (c -> d) -> f a c -> f a d

    first f = bimap f id
    second = bimap id

instance Bifunctor Either where
    bimap f _ (Left a) = Left (f a)
    bimap _ g (Right b) = Right (g b)

instance Bifunctor (,) where
    bimap f g (a,b) = (f a, g b)

Or you could use a Flip combinator like:

newtype Flip f a b = Flip { unFlip :: f b a }

Generalized versions of both of these are available in category-extras on hackage. The latter even includes an instance for Functor (Flip Either a) because Either is a Bifunctor. (I should probably fix that to only require a PFunctor)

Ultimately, the order of arguments in a type constructor is important in determining what classes you can instantiate. You may need to use newtype wrappers (like Flip above) to put the arguments where they need to be to qualify to construct an instance of another typeclass. This is the price we pay for the inference of type class constraints.

like image 35
Edward Kmett Avatar answered Nov 04 '22 12:11

Edward Kmett


You essentially need a 'flip' combinator on types. A newtype wrapper that inverts the order should work, as camccann says. Note that you can't use a 'type' synonym, as they may not be partially applied.

like image 25
Don Stewart Avatar answered Nov 04 '22 12:11

Don Stewart