Search code examples
haskelltypesnestedfunctor

Functor for square matrices


In my previous question here (Nested datatype for square matrices) a datatype Square has been introduced. Now I want to write a functor instance for it. So I begin with map functions for all the four types. I have managed to do so for Nil and Cons:

mapNil :: ((a -> b) -> a -> b) -> (Nil (a -> b) -> Nil a -> Nil b )
mapNil map f Nil = Nil


mapCons :: (forall b . ((a -> b) -> a -> b) -> (t (a -> b) -> t a -> t b))
        -> ((a -> b) -> a -> b)
        -> ((Cons t (a -> b)) -> Cons t a -> Cons t b)
mapCons mapT mapA (Cons f consf) (Cons x consx) =  Cons (mapA f x) (mapT mapA consf consx)

Now, the Square' type:

mapSquare' :: (forall b . ((a -> b) -> a -> b) -> (t (a -> b) -> t a -> t b))
           -> ((a -> b) -> a -> b)
           -> ((Square' t (a -> b)) -> Square' t a -> Square' t b)
mapSquare' mapT mapA (Zero fs) (Zero xs) = Zero (mapT (mapT mapA) fs xs) -- it is wrong
mapSquare' mapT mapA (Succ fs) (Succ xs) = mapSquare' (mapCons mapT) mapA fs xs

And then I will do something like this:

mapSquare = ...

-- this is, actually, the final goal:
instance Functor Square where
fmap = mapSquare

So far, for my mapSquare' Haskell says this:

• Occurs check: cannot construct the infinite type: a ~ t a
  Expected type: (a -> t b) -> a -> t b
    Actual type: t (a -> b) -> t a -> t b
• In the first argument of ‘mapT’, namely ‘(mapT mapA)’
  In the first argument of ‘Zero’, namely ‘(mapT (mapT mapA) fs xs)’
  In the expression: Zero (mapT (mapT mapA) fs xs)

My plan was that (mapT mapA) would "lift" it all between (t (t a)) and (t a) levels. What is my mistake? I would appreciate your help.


Solution

  • You can not use mapSquare since that type is too restrictive. A functor has type fmap :: Functor f => (c -> d) -> f c -> f d. But ((a -> b) -> a -> b) -> (Square (a -> b)) -> Square a -> Square b would only cover a subset of that. Your mapSquare' looks more like a "sequential application" (<*>) :: Applicative f => f (a -> b) -> f a -> f b. Notice that a (<*>) takes two f x items, not one, like the fmap does.

    If you want to declare Square an instance of Functor, it is likely, depending on the way you implement it, that Nil and Const needs to be an instance of Functor as well. We can easily make these an instance of Functor:

    instance Functor Nil where
        fmap _ Nil = Nil
    
    instance Functor t => Functor (Cons t) where
        fmap f (Cons x xs) = Cons (f x) (fmap f xs)

    Now we can make Square' an instance of Functor with:

    instance Functor t => Functor (Square' t) where
        fmap f (Zero x) = Zero (fmap (fmap f) x)
        fmap f (Succ x) = Succ (fmap f x)

    You actually do not generate those yourself. You can use the DeriveFunctor [ghc-doc] compiler option, and let the compiler derive the functions for you:

    {-# LANGUAGE DeriveFunctor #-}
    
    data Square' t a = Zero (t (t a) ) | Succ (Square' (Cons t) a) deriving Functor
    data Nil a = Nil deriving Functor
    data Cons t a = Cons a (t a) deriving Functor

    This will construct the functors specified as here discussed.