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

    -- | General fcf combinators.
    --
    -- See also "Fcf.Data.Function" for more.
    module Fcf.Combinators
      ( Pure
      , Pure1
      , Pure2
      , Pure3
      , type (=<<)
      , type (>>=)
      , type (<=<)
      , LiftM
      , LiftM2
      , LiftM3
      , Join
      , type (<$>)
      , type (<*>)
      , Flip
      , ConstFn
      , type ($)
      ) where

    import Fcf.Core

    -- ** Monadic operations

    infixl 1 >>=
    infixr 1 =<<, <=<
    infixl 4 <$>, <*>

    data Pure :: a -> Exp a
    type instance Eval (Pure x) = x

    data Pure1 :: (a -> b) -> a -> Exp b
    type instance Eval (Pure1 f x) = f x

    data Pure2 :: (a -> b -> c) -> a -> b -> Exp c
    type instance Eval (Pure2 f x y) = f x y

    data Pure3 :: (a -> b -> c -> d) -> a -> b -> c -> Exp d
    type instance Eval (Pure3 f x y z) = f x y z

    data (=<<) :: (a -> Exp b) -> Exp a -> Exp b
    type instance Eval (k =<< e) = Eval (k (Eval e))

    data (>>=) :: Exp a -> (a -> Exp b) -> Exp b
    type instance Eval (e >>= k) = Eval (k (Eval e))

    data (<=<) :: (b -> Exp c) -> (a -> Exp b) -> a -> Exp c
    type instance Eval ((f <=< g) x) = Eval (f (Eval (g x)))

    type LiftM = (=<<)

    data LiftM2 :: (a -> b -> Exp c) -> Exp a -> Exp b -> Exp c
    type instance Eval (LiftM2 f x y) = Eval (f (Eval x) (Eval y))

    data LiftM3 :: (a -> b -> c -> Exp d) -> Exp a -> Exp b -> Exp c -> Exp d
    type instance Eval (LiftM3 f x y z) = Eval (f (Eval x) (Eval y) (Eval z))

    data Join :: Exp (Exp a) -> Exp a
    type instance Eval (Join e) = Eval (Eval e)

    data (<$>) :: (a -> b) -> Exp a -> Exp b
    type instance Eval (f <$> e) = f (Eval e)

    data (<*>) :: Exp (a -> b) -> Exp a -> Exp b
    type instance Eval (f <*> e) = Eval f (Eval e)

    data Flip :: (a -> b -> Exp c) -> b -> a -> Exp c
    type instance Eval (Flip f y x) = Eval (f x y)

    data ConstFn :: a -> b -> Exp a
    type instance Eval (ConstFn a _b) = a

    -- | Note that this denotes the identity function, so @($) f@ can usually be
    -- replaced with @f@.
    data ($) :: (a -> Exp b) -> a -> Exp b
    type instance Eval (($) f a) = Eval (f a)

    infixr 0 $