{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}

module NFunction
    ( NFunction
    , ($*)
    , ComposeN
    , compose
    ) where

import TypeLevelNat ( Z(..), S(..), Nat )
import SizedList    ( SizedList(..) )

-- | A @NFunction n a b@ is a function which takes @n@ arguments of
-- type @a@ and returns a @b@.
-- For example: @NFunction (S (S (S Z))) a b ~ (a -> a -> a -> b)@
type family NFunction n a b :: *

type instance NFunction Z     a b = b
type instance NFunction (S n) a b = a -> NFunction n a b

-- | @f $* xs@ applies the /n/-arity function @f@ to each of the arguments in
-- the /n/-sized list @xs@.
($*) :: NFunction n a b -> SizedList n a -> b
f $* Nil        = f
f $* (x ::: xs) = f x $* xs

infixr 0 $* -- same as $

class Nat n => ComposeN n where
    -- | Composition of NFunctions.
    --
    -- Note that the @n@ and @a@ arguments are used by the type
    -- checker to select the right @ComposeN@ instance. They are
    -- usally given as @(witnessNat :: n)@ and @(undefined :: a)@.
    compose :: forall a b c. n -> a
            -> (b -> c) -> NFunction n a b -> NFunction n a c

instance ComposeN Z where
    compose Z _ = ($)

instance ComposeN n => ComposeN (S n) where
    compose (S n) (_ :: a) f g = compose n (undefined :: a) f . g

{-
TODO: The following does not work as expected.
See: http://www.haskell.org/pipermail/haskell-cafe/2009-August/065850.html

-- | @f .* g@ composes @f@ with the /n/-arity function @g@.
(.*) :: forall n a b c. (ComposeN n) => (b -> c) -> NFunction n a b -> NFunction n a c
(.*) = compose (witnessNat :: n) (undefined :: a)

infixr 9 .* -- same as .
-}