    {-# LANGUAGE
        DataKinds,
        PolyKinds,
        TypeFamilies,
        TypeInType,
        TypeOperators,
        UndecidableInstances #-}

    module Fcf.Class.Functor
      ( Map
      , FMap
      ) where

    import Fcf.Core (Exp, Eval)

    -- $setup
    -- >>> :set -XUndecidableInstances -XTypeInType
    -- >>> import Fcf.Core (Eval, Exp)
    -- >>> import Fcf.Data.Nat
    -- >>> import qualified GHC.TypeLits as TL

    -- | Type-level 'fmap' for type-level functors.
    --
    -- Note: this name clashes with 'Data.Map.Lazy.Map' from /containers/.
    -- 'FMap' is provided as a synonym to avoid this.
    --
    -- === __Example__
    --
    -- >>> data AddMul :: Nat -> Nat -> Exp Nat
    -- >>> type instance Eval (AddMul x y) = (x TL.+ y) TL.* (x TL.+ y)
    -- >>> :kind! Eval (Map (AddMul 2) '[0, 1, 2, 3, 4])
    -- Eval (Map (AddMul 2) '[0, 1, 2, 3, 4]) :: [Nat]
    -- = '[4, 9, 16, 25, 36]
    data Map :: (a -> Exp b) -> f a -> Exp (f b)

    -- | Synonym of 'Map' to avoid name clashes.
    type FMap = Map

    -- []
    type instance Eval (Map f '[]) = '[]
    type instance Eval (Map f (a ': as)) = Eval (f a) ': Eval (Map f as)

    -- Maybe
    type instance Eval (Map f 'Nothing) = 'Nothing
    type instance Eval (Map f ('Just a)) = 'Just (Eval (f a))

    -- Either
    type instance Eval (Map f ('Left x)) = 'Left x
    type instance Eval (Map f ('Right a)) = 'Right (Eval (f a))

    -- Tuples
    type instance Eval (Map f '(x, a)) =
      '(x, Eval (f a))
    type instance Eval (Map f '(x, y, a)) =
      '(x, y, Eval (f a))
    type instance Eval (Map f '(x, y, z, a)) =
      '(x, y, z, Eval (f a))
    type instance Eval (Map f '(x, y, z, w, a)) =
      '(x, y, z, w, Eval (f a))