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

    -- | Simple combinators for functions.
    module Fcf.Data.Function
      ( type (&)
      , On
      , Bicomap
      ) where

    import Fcf.Core

    infixl 1 &

    -- $setup
    -- >>> import Fcf.Combinators (Pure)
    -- >>> import Fcf.Data.Common (Fst)
    -- >>> import Fcf.Data.Bool (type (&&), type (||))

    -- | Reverse function application, argument first.
    --
    -- === __Example__
    --
    -- >>> :kind! Eval ('( 'True, 'Nothing) & Fst)
    -- Eval ('( 'True, 'Nothing) & Fst) :: Bool
    -- = 'True
    data (&) :: a -> (a -> Exp b) -> Exp b
    type instance Eval (x & f) = Eval (f x)

    -- | Lift a binary function to the domain of a projection.
    --
    -- === __Example__
    --
    -- >>> :kind! Eval (((&&) `On` Fst) '( 'True, 'Nothing) '( 'False, 'Just '()))
    -- Eval (((&&) `On` Fst) '( 'True, 'Nothing) '( 'False, 'Just '())) :: Bool
    -- = 'False
    data On :: (b -> b -> Exp c) -> (a -> Exp b) -> a -> a -> Exp c
    type instance Eval (On r f x y) = Eval (r (Eval (f x)) (Eval (f y)))

    -- | Pre-compose a binary function with a function for each argument.
    --
    -- === __Example__
    --
    -- >>> :kind! Eval (Bicomap Fst Pure (||) '( 'False, 'Nothing) 'True)
    -- Eval (Bicomap Fst Pure (||) '( 'False, 'Nothing) 'True) :: Bool
    -- = 'True
    data Bicomap :: (a -> Exp c) -> (b -> Exp d) -> (c -> d -> Exp e) -> a -> b -> Exp e
    type instance Eval (Bicomap f g r x y) = Eval (r (Eval (f x)) (Eval (g y)))