Making a function an instance of functor

88 Views Asked by At

I'm attempting to implement functor for a record object that has a function attribute, something like this:

data Function a =
    Function {
        , funcApply :: FData -> [Exp a] -> Either (RawException a) (Exp a)
    }

The funcApply attribute therefore needs to fmap to the type FData -> [Exp b] -> Either (RawException b) (Exp b).

Mapping over the return values seems simple enough, and gets me to the type FData -> [Exp a] -> Either (RawException b) (Exp b):

instance Functor Function where
    fmap f (Function funcApply) =
        let funcApply' = \fData exps ->
                            case funcApply fData exps of
                                Left x -> Left $ fmap f x
                                Right x -> Right $ fmap f x
        in Function funcApply'

But I really can't see how to map over the function arguments. Logically, it feels like funcApply shouldn't need to be changed at all, as it's already polymorphic, but it seems that the compiler disagrees.

Any help gratefully received!

1

There are 1 best solutions below

1
Iceland_jack On BEST ANSWER

The type should be designed with regard to the desired instance. You figure out what mapping is useful for your purpose and structure your type to reflect it. As has been pointed out it is not possible to be Functorial in an argument that appears with mixed polarity.

{-# Language DeriveFunctor            #-}
{-# Language DerivingStrategies       #-}
{-# Language StandaloneKindSignatures #-}

import Data.Kind

-- • Can't make a derived instance of
--     ‘Functor Endo’ with the stock strategy:
--     Constructor ‘Endo’ must not use the type variable in a function argument
type    Endo :: Type -> Type
newtype Endo a = Endo (a -> a)
  deriving stock Functor

A common trick is to split the argument by polarity,

--                  (<–)    (->)    (->)
--                  |       |       |
type    Function :: Type -> Type -> Type
newtype Function іn out = Function (FData -> [Exp іn] -> Either (RawException out) (Exp out))

instance Profunctor Function where
  --           Contravariant   Covariant
  --           vv              vv
  dimap :: (іn <– іn') -> (out -> out') -> (Function іn out -> Function іn' out')
  dimap pre post (Function fn) = Function \a bs ->
    bimap (fmap post) (fmap post) (fn a (map (fmap pre) bs))

Then you also get a valid Functor (Function іn)

instance Functor (Function іn) where
  fmap :: (out -> out') -> (Function іn out -> Function іn out')
  fmap = rmap

Your solution from the comment, to make it polymorphic also works. It means there is no parameter to map over so it can't have higher-kinded instances like Functor or Profunctor.

type    Function :: Type
newtype Function = Function
  (forall a. FData -> [Exp a] -> Either (RawException a) (Exp a))

Being polymorphic is a double-edged sword. You are free to instantiate it however you want \(Function fn) -> fn @(Int -> Int) but it places severe restrictions on what counts as a function.