gongju-0.1.0.0: src/Gongju/Positional.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
-- | Module: Gongju.Positional
-- Description: Guarded arrow unification and positional combinators (Yank, Shove, Twist)
--
-- Provides inductive combinators for manipulating curried function arguments by position:
-- pulling arguments to the front (Yank), pushing arguments deeper (Shove), and swapping
-- adjacent arguments (Twist), accompanied by guarded error messages for arity mismatches.
module Gongju.Positional
( -- * Guarded Arrow Unification
CheckArrow
, ExpectArrow
-- * Yank (Pull to Front)
, Yank(..)
, slipr
, yank
-- * Shove (Push Deeper)
, Shove(..)
, slipl
, shove
-- * Twist (Adjacent Swap)
, Twist(..)
, twist
) where
import Data.Kind (Constraint, Type)
import GHC.TypeLits
( ErrorMessage (ShowType, Text, (:$$:), (:<>:))
, Nat
, Symbol
, TypeError
)
import Gongju.Peano (Peano(..), ToPeano)
{- Note [Positional Curried Arrow Manipulation]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-positional-arrow-manipulation]
Manipulating the positional arguments of curried functions (a0 -> a1 -> ... -> r) is
conventionally approached by either:
1. Converting to an n-tuple via 'Generics.SOP' / 'tuple-sop', which requires finite
tuple instances and loses bidirectional type inference.
2. Generating dozens of ad-hoc permutations via Template Haskell.
Gongju manipulates curried function arrows directly in place using three fundamental
primitives:
1. 'Yank' ('slipr'): Pulls the n-th argument (0-indexed) to position 0, shifting
intervening arguments [0 .. n-1] one slot to the right.
2. 'Shove' ('slipl'): Pushes argument 0 into depth n, shifting intervening arguments
[1 .. n] one slot to the left.
3. 'Twist' ('twist'): Transposes adjacent arguments at depth n and n+1.
Furthermore, 'CheckArrow' and 'ExpectArrow' provide compile-time guard rails that report
clear, descriptive custom type errors when arity boundaries are exceeded.
-}
-- | Guard family asserting that type @f@ is a function arrow @(a -> b)@.
type family CheckArrow (ctx :: Symbol) (f :: Type) :: Constraint where
CheckArrow _ (_ -> _) = ()
CheckArrow ctx nonFn =
TypeError
( 'Text "• [" :<>: 'Text ctx :<>: 'Text "] Arity / Index Mismatch:"
':$$: 'Text " Expected a function (a -> r), but reached: " :<>: 'ShowType nonFn
)
-- | Constraint requiring @f@ to unify with @(a -> r)@ with diagnostic context @ctx@.
class (CheckArrow ctx f, f ~ (a -> r)) => ExpectArrow (ctx :: Symbol) f a r
instance (CheckArrow ctx f, f ~ (a -> r)) => ExpectArrow ctx f a r
-- | Pull the @n@-th curried argument (0-indexed) to the front position.
--
-- See Note [Positional Curried Arrow Manipulation] [ref:note-positional-arrow-manipulation].
class Yank (n :: Peano) f target rest | n f -> target rest where
-- | Internal worker for pulling the @n@-th argument forward.
yank' :: f -> target -> rest
instance ExpectArrow "Yank" (a -> r) a r => Yank 'Z (a -> r) a r where
yank' f target = f target
{-# INLINE yank' #-}
instance (ExpectArrow "Yank" (a -> r) a r, Yank n r target rest')
=> Yank ('S n) (a -> r) target (a -> rest') where
yank' f target a = yank' @n (f a) target
{-# INLINE yank' #-}
-- | Pull the @n@-th argument (0-indexed) to position 0.
--
-- Equivalent to @slipr@ in 'composition-extra'.
slipr :: forall (n :: Nat) f target rest. Yank (ToPeano n) f target rest => f -> target -> rest
slipr = yank' @(ToPeano n)
{-# INLINE slipr #-}
-- | User-facing synonym for 'slipr': pulls the @n@-th argument (0-indexed) to position 0.
yank :: forall (n :: Nat) f target rest. Yank (ToPeano n) f target rest => f -> target -> rest
yank = yank' @(ToPeano n)
{-# INLINE yank #-}
-- | Push the 0-th argument into position @n@, shifting arguments 1 through @n@ to the left.
class Shove (n :: Peano) a rest f | n a rest -> f, n f -> a rest where
-- | Internal worker for pushing the head argument deeper.
shove' :: (a -> rest) -> f
instance Shove 'Z a r (a -> r) where
shove' = id
{-# INLINE shove' #-}
instance Shove n a rest r => Shove ('S n) a (b -> rest) (b -> r) where
shove' f b = shove' @n (`f` b)
{-# INLINE shove' #-}
-- | Push the 0-th argument into position @n@.
--
-- Equivalent to @slipl@ in 'composition-extra'.
slipl :: forall (n :: Nat) a rest f. Shove (ToPeano n) a rest f => (a -> rest) -> f
slipl = shove' @(ToPeano n)
{-# INLINE slipl #-}
-- | User-facing synonym for 'slipl': pushes the 0-th argument into position @n@.
shove :: forall (n :: Nat) a rest f. Shove (ToPeano n) a rest f => (a -> rest) -> f
shove = shove' @(ToPeano n)
{-# INLINE shove #-}
-- | Swap adjacent arguments at depth @n@ and @n + 1@.
class Twist (n :: Peano) f g | n f -> g, n g -> f where
-- | Internal worker for transposing adjacent arguments.
twist' :: f -> g
instance Twist 'Z (a -> b -> r) (b -> a -> r) where
twist' = flip
{-# INLINE twist' #-}
instance Twist n r r' => Twist ('S n) (a -> r) (a -> r') where
twist' f a = twist' @n (f a)
{-# INLINE twist' #-}
-- | Swap adjacent arguments at depth @n@ and @n + 1@.
twist :: forall (n :: Nat) f g. Twist (ToPeano n) f g => f -> g
twist = twist' @(ToPeano n)
{-# INLINE twist #-}