{-# 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
-- >>> :set -XTypeFamilies -XDataKinds -XTypeOperators
-- >>> import Fcf.Core
-- >>> 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)))