Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell defining Functor instance for an alternative Either data type

Going through Typeclassopedia to gain some routing working with type classes. Want to make an alternative to Either an instance of Functor, but even examining the definition of Either as an instance of Functor keeps getting me in trouble. Have this, but will not compile.

data Alt a b = Success a | Failure b deriving (Show, Eq, Ord) 

instance Functor (Alt a) where 
  fmap _ (Failure a) = Failure a
  fmap f (Success x) = Success (f x)  

    • Couldn't match expected type ‘a1’ with actual type ‘a’
      ‘a1’ is a rigid type variable bound by
        the type signature for:
          fmap :: forall a1 b. (a1 -> b) -> Alt a a1 -> Alt a b
        at Brenty_tcop.hs:25:3-6
      ‘a’ is a rigid type variable bound by
        the instance declaration
        at Brenty_tcop.hs:24:10-24
    • In the first argument of ‘f’, namely ‘x’
      In the first argument of ‘Success’, namely ‘(f x)’
      In the expression: Success (f x)
    • Relevant bindings include
        x :: a (bound at Brenty_tcop.hs:26:19)
        f :: a1 -> b (bound at Brenty_tcop.hs:26:8)
        fmap :: (a1 -> b) -> Alt a a1 -> Alt a b
          (bound at Brenty_tcop.hs:25:3)
   |
26 |   fmap f (Success x) = Success (f x) 
like image 306
Madderote Avatar asked Mar 26 '19 19:03

Madderote


People also ask

What is functor in Haskell?

Functor in Haskell is a kind of functional representation of different Types which can be mapped over. It is a high level concept of implementing polymorphism. According to Haskell developers, all the Types such as List, Map, Tree, etc. are the instance of the Haskell Functor.

Is tuple a functor?

Tuple as a functor Doing this for a pair, which in C# has the type Tuple<T, U> , this means that tuples are functors if we keep T fixed and enable translation of the second element from U1 to U2 . You simply return a new tuple by carrying source.

Is string a functor?

String is not a functor, because it has the wrong kind. String :: Type , but a functor has to have kind Type -> Type . Before we talk about Tree , let's establish a few facts. Sum types are functors, if the components are functors.

Is set a functor?

The functor laws express that a translation of functions preserves the structure of how the functions compose, in addition to preserving the structure of the containers. Mapping a set doesn't preserve those structures, and that's the reason that sets aren't functors.


1 Answers

As @chepner says in the comments, your code will compile if you switch the order of the type parameters,

data Alt b a = Success a | Failure b

or alternatively switch the meaning of the Functor instance, so that it maps over Failure and leaves Success alone.

instance Functor (Alt a) where
    fmap f (Success x) = Success x
    fmap f (Failure x) = Failure (f x)

Basically, the Functor type class only knows how to map over a type's last type parameter. So we had to rejig things so that we apply the function f to an occurrence of that last type parameter.


Why you can only map over the rightmost parameter is a very deep and interesting question. To understand this you have to understand kinds, which are an advanced feature of Haskell's type system.

You can think of kinds as being the "next level" of types, in some sense. Types classify values; kinds classify types. So "foo" is a String, and String is a type. In Haskell "type" is pronounced *.

-- :t in ghci asks for the type of a value-level expression
ghci> :t "foo"
"foo" :: String

-- :k asks for the kind of a type-level expression
ghci> :k String
String :: *

All ordinary types - those which can have values - have a kind of *. So String :: *, Int :: *, Bool :: *, etc.

Things get interesting when you start thinking about parameterised types. Maybe is not a type by itself - you can't have values of type Maybe, but you can have Maybe Int, Maybe String, etc. So Maybe is a sort of function - it takes a type as an argument and it produces a type. (Maybe is a type constructor, to use the technical term.)

-- Maybe is a function...
ghci> :k Maybe
Maybe :: * -> *

-- and you can apply it to an argument to get a type
ghci> :k Maybe Int
Maybe Int :: *

Alt is a two-parameter type function. Type functions are curried in Haskell, just like regular value functions, so Alt has a type of * -> * -> * (which really means * -> (* -> *)).

ghci> :k Alt
Alt :: * -> * -> *

Now, Functor is a higher-order type function. It takes an argument f, which itself is a type function. Functor on its own is not a valid type class constraint, but Functor f is.

ghci> :k Functor
Functor :: (* -> *) -> Constraint

This means Maybe on its own, with a kind of * -> *, is a valid argument for the Functor type function. But Int :: * isn’t, and nor is Maybe Int :: *, and nor is Alt :: * -> * -> *. The error messages tell you about the kind mismatch:

ghci> :k Functor Int
<interactive>:1:9: error:
    • Expected kind ‘* -> *’, but ‘Int’ has kind ‘*’
    • In the first argument of ‘Functor’, namely ‘Int’
      In the type ‘Functor Int’

ghci> :k Functor Alt
<interactive>:1:9: error:
    • Expecting one more argument to ‘Alt’
      Expected kind ‘* -> *’, but ‘Alt’ has kind ‘* -> * -> *’
    • In the first argument of ‘Functor’, namely ‘Alt’
      In the type ‘Functor Alt’

The kind system is there to prevent you from forming invalid types, just like how the type system prevents you from writing invalid values. If there was no kind system, and we were allowed to write instance Functor Alt, it would produce the following (nonsensical) type for fmap:

-- `Alt a` is not a valid type, because its second argument is missing!
fmap :: (a -> b) -> Alt a -> Alt b

So we need to turn Alt :: * -> * -> * into something of kind * -> *, in order to have a valid argument for Functor. Alt is a curried type function, so if we give it a single type argument, we'll get a type function back!

ghci> :k Functor (Alt Int)
Functor (Alt Int) :: Constraint

That's why the instance declaration says instance Functor (Alt x) - it needs to give Alt an argument (and in this case the argument can be any type x as long as its kind is *). Now we have fmap :: (a -> b) -> Alt x a -> Alt x b, which is a valid type expression.

So in general, the recipe for making a Functor instance is to start by giving arguments to your type until it only has one parameter left. That's why Functor only knows how to map over the rightmost type parameter. As an exercise you can try defining a Functor class which maps over the second-to-last type parameter.

This is a big topic so hopefully I haven't gone too fast. It's OK not to understand kinds straight away - it took me several tries! Let me know in the comments if there's anything you'd like me to explain further.

like image 148
Benjamin Hodgson Avatar answered Oct 05 '22 11:10

Benjamin Hodgson