{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE Safe                   #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE UndecidableInstances   #-}

-- | Provides operator of variable-arguments function composition.

module Universum.VarArg
       ( SuperComposition(..)
       ) where

-- $setup
-- >>> import Universum.Base ((+))
-- >>> import Universum.Container (null)
-- >>> import Prelude (show)
-- >>> import Data.List (zip5)

-- | This type class allows to implement variadic composition operator.
class SuperComposition a b c | a b -> c where
    -- | Allows to apply function to result of another function with multiple
    -- arguments.
    --
    -- >>> (show ... (+)) 1 2
    -- "3"
    -- >>> show ... 5
    -- "5"
    -- >>> (null ... zip5) [1] [2] [3] [] [5]
    -- True
    --
    -- Inspired by <http://stackoverflow.com/questions/9656797/variadic-compose-function>.
    --
    -- ==== Performance
    -- To check the performance there was done a bunch of benchmarks. Benchmarks were made on
    -- examples given above and also on the functions of many arguments.
    -- The results are showing that the operator ('...') performs as fast as
    -- plain applications of the operator ('Prelude..') on almost all the tests, but ('...')
    -- leads to the performance draw-down if @ghc@ fails to inline it.
    -- Slow behavior was noticed on functions without type specifications.
    -- That's why keep in mind that providing explicit type declarations for functions is very
    -- important when using ('...').
    -- Relying on type inference will lead to the situation when all optimizations
    -- disappear due to very general inferred type. However, functions without type
    -- specification but with applied @INLINE@ pragma are fast again.
    --
    (...) :: a -> b -> c

infixl 8 ...

instance {-# INCOHERENT #-} (a ~ c, r ~ b) =>
         SuperComposition (a -> b) c r where
    a -> b
f ... :: (a -> b) -> c -> r
... c
g = a -> b
f c
g
    {-# INLINE (...) #-}

instance {-# INCOHERENT #-} (SuperComposition (a -> b) d r1, r ~ (c -> r1)) =>
         SuperComposition (a -> b) (c -> d) r where
    (a -> b
f ... :: (a -> b) -> (c -> d) -> r
... c -> d
g) c
c = a -> b
f forall a b c. SuperComposition a b c => a -> b -> c
... c -> d
g c
c
    {-# INLINE (...) #-}