packages feed

variadic-function-0.1.0.0: src/Data/Function/Variadic.hs

module Data.Function.Variadic
  ( -- * Decomposition and creation of functions
    Function (..),
    ConstructFunction,
    DeconstructFunction,

    -- * Helper classes for argument constraints
    EmptyConstraint,
    type (&),
  )
where

import Data.Kind (Type)

-- | Toolkit for creating and transforming functions with a variable number of arguments.
-- Its parameters are function, list of its arguments, its result, and `argC`
-- that constraints all arguments of the function.
class
  (ConstructFunction args r ~ f, DeconstructFunction f ~ '(args, r)) =>
  Function f args r argC
  where
  -- | Create a new function
  --
  -- === __Example usage__
  --
  -- >>> printf :: Function Show f args String => f
  -- >>> printf = createFunction (Proxy :: Proxy Show) (\acc a -> acc <> show a) id ""
  -- >>> printf "hello" () :: String
  -- "hello()"
  createFunction ::
    -- | Required for unambiguous choice of Function instance
    proxy argC ->
    -- | Combine arguments with accumulator
    (forall a. argC a => acc -> a -> acc) ->
    -- | Make result of the function
    (acc -> r) ->
    -- | Accumulator
    acc ->
    f

  -- | Create a function with the same arguments as given one but may have a different result.
  transformFunction ::
    -- | Required for unambiguous choice of the Function instance
    proxy argC ->
    -- | Combine arguments with accumulator
    (forall a. argC a => acc -> a -> acc) ->
    -- | Create result of the `f` function using accumulator and the result of the function to transform
    (acc -> r0 -> r) ->
    -- | Accumulator
    acc ->
    -- | The function to transform
    ConstructFunction args r0 ->
    -- | The new function
    f

-- | Extract list of arguments and the result from the function.
type family DeconstructFunction (f :: Type) :: ([Type], Type) where
  DeconstructFunction (a -> f) = MapFst ((:) a) (DeconstructFunction f)
  DeconstructFunction x = '( '[], x)

type family MapFst (f :: k1 -> k1) (tuple :: (k1, k2)) where
  MapFst f '(a, b) = '(f a, b)

-- | Given the types of function arguments and its result, make a type of a function.
type family ConstructFunction (args :: [Type]) (r :: Type) where
  ConstructFunction '[] r = r
  ConstructFunction (a : args) r = a -> ConstructFunction args r

instance
  ('( '[], r) ~ DeconstructFunction r) =>
  Function r '[] r argC
  where
  createFunction _ _ fr r = fr r
  transformFunction _ _ fr acc r = fr acc r

instance
  (Function f args r argC, argC a) =>
  Function (a -> f) (a : args) r argC
  where
  createFunction pArgC fa fr acc = createFunction pArgC fa fr . fa acc
  transformFunction pArgC fa fr acc f = \a -> transformFunction pArgC fa fr (fa acc a) (f a)

-- | When the arguments are not constrained, use this as the argC parameter of `Function`.
class EmptyConstraint a

instance EmptyConstraint a

-- | Combine constraints. For example, @Function f args x (Show & Num)@.
class (f x, g x) => (&) f g (x :: k)

instance (f x, g x) => (&) f g x