{-# LANGUAGE TypeFamilies            #-}
{-# LANGUAGE MultiParamTypeClasses   #-}
{-# LANGUAGE TypeOperators           #-}
{-# LANGUAGE FlexibleInstances       #-}
{-# LANGUAGE FlexibleContexts        #-}
{-# LANGUAGE ScopedTypeVariables     #-}
{-# LANGUAGE UndecidableInstances    #-}
{-# LANGUAGE TypeApplications        #-}
{-# LANGUAGE TypeInType              #-}
{-# LANGUAGE AllowAmbiguousTypes     #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Function.NAry
-- Copyright   :  (C) 2017 Alexey Vagarenko
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  Alexey Vagarenko (vagarenko@gmail.com)
-- Stability   :  experimental
-- Portability :  non-portable
--
----------------------------------------------------------------------------

module Data.Function.NAry (
      NAry
    , ApplyNAry(..)
) where

import Data.Kind                    (Type)
import Data.Proxy                   (Proxy(..))
import GHC.TypeLits                 (Nat, type (-), natVal, KnownNat)

---------------------------------------------------------------------------------------------------
-- | N-ary function from @n@ arguments of type @t@ to value of type @r@.
type family NAry (n :: Nat) (t :: Type) (r :: Type) :: Type where
    NAry 0 t r = r
    NAry n t r = t -> (NAry (n - 1) t r)

-- | Apply list of params to N-ary function.
class ApplyNAry (n :: Nat) (t :: Type) (r :: Type) where
    applyNAry :: NAry n t r -> [t] -> r

instance {-# OVERLAPPING #-} ApplyNAry 0 t r where
    applyNAry r _ = r
    {-# INLINE applyNAry #-}

instance {-# OVERLAPPABLE #-}
    (ApplyNAry (n - 1) t r, NAry n t r ~ (t -> NAry (n - 1) t r), KnownNat n)
    => ApplyNAry n t r where
    applyNAry f (x : xs) = applyNAry @(n - 1) @t @r (f x) xs
    applyNAry _ []       = error $ "Not enough params to apply to " ++ show (natVal (Proxy @n)) ++ "-ary function."
    {-# INLINE applyNAry #-}