-- Church-encoded lists. Used in Twee.CP to make sure that fusion happens.
{-# LANGUAGE Rank2Types, BangPatterns #-}
module Data.ChurchList where

import Prelude(Functor(..), Applicative(..), Monad(..), Bool(..), Maybe(..), (.), ($), id)
import qualified Prelude
import GHC.Magic(oneShot)
import GHC.Exts(build)
import Control.Monad(MonadPlus(..), liftM2)
import Control.Applicative(Alternative(..))

newtype ChurchList a =
  ChurchList (forall b. (a -> b -> b) -> b -> b)

{-# INLINE foldr #-}
foldr :: (a -> b -> b) -> b -> ChurchList a -> b
foldr op e (ChurchList f) = eta (f op (eta e))
  -- Using eta here seems to help with eta-expanding foldl'

{-# INLINE[0] eta #-}
eta :: a -> a
eta x = x
{-# RULES "eta" forall f. eta f = \x -> f x #-}

{-# INLINE nil #-}
nil :: ChurchList a
nil = ChurchList (\_ n -> n)

{-# INLINE unit #-}
unit :: a -> ChurchList a
unit x = ChurchList (\c n -> c x n)

{-# INLINE cons #-}
cons :: a -> ChurchList a -> ChurchList a
cons x xs = ChurchList (\c n -> c x (foldr c n xs))

{-# INLINE append #-}
append :: ChurchList a -> ChurchList a -> ChurchList a
append xs ys = ChurchList (\c n -> foldr c (foldr c n ys) xs)

{-# INLINE join #-}
join :: ChurchList (ChurchList a) -> ChurchList a
join xss = ChurchList (\c n -> foldr (\xs ys -> foldr c ys xs) n xss)

instance Functor ChurchList where
  {-# INLINE fmap #-}
  fmap f xs = ChurchList (\c n -> foldr (c . f) n xs)

instance Applicative ChurchList where
  {-# INLINE pure #-}
  pure = return
  {-# INLINE (<*>) #-}
  (<*>) = liftM2 ($)

instance Monad ChurchList where
  {-# INLINE return #-}
  return = unit
  {-# INLINE (>>=) #-}
  xs >>= f = join (fmap f xs)

instance Alternative ChurchList where
  {-# INLINE empty #-}
  empty = nil
  {-# INLINE (<|>) #-}
  (<|>) = append

instance MonadPlus ChurchList where
  {-# INLINE mzero #-}
  mzero = empty
  {-# INLINE mplus #-}
  mplus = (<|>)

{-# INLINE fromList #-}
fromList :: [a] -> ChurchList a
fromList xs = ChurchList (\c n -> Prelude.foldr c n xs)

{-# INLINE toList #-}
toList :: ChurchList a -> [a]
toList (ChurchList f) = build f

{-# INLINE foldl' #-}
foldl' :: (b -> a -> b) -> b -> ChurchList a -> b
foldl' op e xs =
  foldr (\x f -> oneShot (\ (!acc) -> f (op acc x))) id xs e

{-# INLINE filter #-}
filter :: (a -> Bool) -> ChurchList a -> ChurchList a
filter p xs =
  ChurchList $ \c n ->
    let
      {-# INLINE op #-}
      op x xs = if p x then c x xs else xs
    in
      foldr op n xs

{-# INLINE fromMaybe #-}
fromMaybe :: Maybe a -> ChurchList a
fromMaybe Nothing = nil
fromMaybe (Just x) = unit x