packages feed

gongju-0.1.0.0: src/Gongju/Permute.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

-- | Module: Gongju.Permute
-- Description: Arbitrary curried function argument permutations via iterated Yank
--
-- Enables reordering curried function arguments arbitrarily according to a type-level
-- permutation list, without tuple-size limits or type-directed ambiguity.
module Gongju.Permute
  ( -- * Type Families
    Adjust
  , MapAdjust
  , MapToPeano
    -- * Arbitrary Permutation
  , Permute(..)
  , permute
  ) where

import GHC.TypeLits (Nat)
import Gongju.Peano (Peano(..), ToPeano)
import Gongju.Positional (Yank(..))

{- Note [Iterated Yank Index Adjustment]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-iterated-yank-adjustment]
Arbitrary argument permutation (e.g. taking f :: a -> b -> c -> d -> r and producing
c -> a -> d -> b -> r via @'[2, 0, 3, 1]) is achieved by decomposing the permutation
into sequential 'Yank' operations.

When an argument at index `p` is pulled to position 0:
  1. Any remaining original target index `i > p` shifts down by 1 in the residual function.
  2. Any remaining original target index `i < p` retains its relative position.

The closed type family 'Adjust' recalculates remaining indices at each inductive step,
and 'Permute' recurses over the updated index list. This provides:
  - Zero tuple size limits (functions of arity 10, 20, 50 can be reordered).
  - Immunity to duplicate types (e.g. Int -> Int -> Int -> r).
  - Support for partial permutations (arguments not mentioned in the permutation list
    remain in their original relative positions at the tail).
-}

-- | Recalculate index @i@ after an element at position @picked@ has been removed.
--
-- See Note [Iterated Yank Index Adjustment] [ref:note-iterated-yank-adjustment].
type family Adjust (picked :: Peano) (i :: Peano) :: Peano where
  Adjust 'Z     ('S i) = i
  Adjust ('S p) ('S i) = 'S (Adjust p i)
  Adjust _      i      = i

-- | Map 'Adjust' over a type-level list of Peano indices.
type family MapAdjust (p :: Peano) (ps :: [Peano]) :: [Peano] where
  MapAdjust _ '[]       = '[]
  MapAdjust p (i ': is) = Adjust p i ': MapAdjust p is

-- | Convert a type-level list of 'Nat' indices to inductive 'Peano' numbers.
type family MapToPeano (ns :: [Nat]) :: [Peano] where
  MapToPeano '[]       = '[]
  MapToPeano (n ': ns) = ToPeano n ': MapToPeano ns

-- | Inductive class computing the permutation of curried function arguments.
class Permute (ps :: [Peano]) f g | ps f -> g where
  -- | Internal worker for executing iterated permutation.
  permute' :: f -> g

instance Permute '[] f f where
  permute' = id
  {-# INLINE permute' #-}

instance (Yank p f arg rest, Permute (MapAdjust p ps) rest rest')
  => Permute (p ': ps) f (arg -> rest') where
  permute' f arg = permute' @(MapAdjust p ps) (yank' @p f arg)
  {-# INLINE permute' #-}

-- | Reorder the arguments of a curried function according to a 0-indexed list of 'Nat' positions.
--
-- Example:
--
-- >>> let f x y z = (x, y, z)
-- >>> permute @'[2, 0, 1] f 3 1 2
-- (1, 2, 3)
permute :: forall (ns :: [Nat]) f g. Permute (MapToPeano ns) f g => f -> g
permute = permute' @(MapToPeano ns)
{-# INLINE permute #-}