Search code examples
haskellfunctor

fmap over variable argument function


I would like to define fmap on variable argument functions:

type family VarArg (args :: [*]) e where
  VarArg '[] e = e
  VarArg (a ': as) e = a -> VarArg as e

mapVarArg :: forall args e e'
           . (e -> e') -> VarArg args e -> VarArg args e'
mapVarArg f = _

This is the closest solution I have found:

mapVarArg :: forall args e e' . VarArgIso args
          => (e -> e') -> VarArg args e -> VarArg args e'
mapVarArg f = (^. Lens.from varArgIso) . fmap f . (^. varArgIso @args)

data VarArgD (args :: [*]) e where
  DNil  :: e -> VarArgD '[] e
  DCons :: (a -> VarArgD as e) -> VarArgD (a ': as) e

class VarArgIso (args :: [*]) where
  varArgIso :: Iso' (VarArg args e) (VarArgD args e)

instance VarArgIso '[] where
  varArgIso = iso DNil (\(DNil x) -> x)

instance VarArgIso as => VarArgIso (a ': as) where
  varArgIso = iso (\f -> DCons ((^. varArgIso) . f)) (\(DCons f) -> ((^. Lens.from varArgIso) . f))

instance Functor (VarArgD args) where
  fmap f (DNil a)  = DNil (f a)
  fmap f (DCons g) = DCons (fmap f . g)

Is there a simpler solution, or any solution without an additional VarArgIso constraint?


Solution

  • I think non-template solutions are not possible without additional class constraints. There is a simple and presumably efficient implementation with overlapping instances:

    class VarArg a b c d where
      mapVarArg :: (a -> b) -> c -> d
    
    instance (a ~ c, b ~ d) => VarArg a b c d where
      mapVarArg = id
    
    instance {-# overlapping #-} (VarArg a b c2 d2, c1 ~ d1) =>
      VarArg a b (c1 -> c2) (d1 -> d2) where
      mapVarArg f g = mapVarArg f . g
    

    If you replace overlapping with incoherent, it also generally works with polymorphic/constrained functions as arguments:

    > mapVarArg (+100) (+) 0 0
    100
    

    However, with the incoherent instance, partially applied mapVarArg-s tend to have unusable inferred types.

    > let foo = mapVarArg (+100) (+)
    > :t foo
    foo :: (Num (a -> a -> a), Num a) => a -> a -> a