packages feed

large-records-0.1.0.0: src/Data/Record/TH/CodeGen/Tree.hs

{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Record.TH.CodeGen.Tree (
    -- * Trees and forests
    Tree(..)
  , Forest(..)
    -- * Catamorphisms
  , Cata(..)
  , tree
  , forest
    -- * Dealing with @ghc@'s tuple size limit
  , TupleLimit(..)
  , nest
  , mkTupleE
  , mkTupleT
  , mkTupleP
  ) where

import Language.Haskell.TH

import Data.Record.Internal.TH.Util

{-------------------------------------------------------------------------------
  Trees and forests
-------------------------------------------------------------------------------}

-- | Trees with values at the leaves
data Tree   a = Leaf a | Branch (Forest a) deriving (Show)
data Forest a = Forest [Tree a]            deriving (Show)

{-------------------------------------------------------------------------------
  Catamorphisms

  Unlike regular folds, these catamorphisms are structure preserving.
  See "Dealing with Large Bananas", by Ralf Lämmel et al
-------------------------------------------------------------------------------}

data Cata a b = Cata {
      leaf   :: a -> b
    , branch :: [b] -> b
    }

tree :: Cata a b -> Tree a -> b
tree cata (Leaf   a)  = leaf   cata a
tree cata (Branch as) = forest cata as

forest :: Cata a b -> Forest a -> b
forest cata (Forest ts) = branch cata (map (tree cata) ts)

{-------------------------------------------------------------------------------
  Nesting
-------------------------------------------------------------------------------}

-- | Observe @ghc@'s tuple length
--
-- Haskell has a limit of 62 fields per tuple. Here we take an arbitrary
-- list and turn it into a nested tuple that preserves this limit.
--
-- Example: if we reduce the limit to @2@, we get the following nestings,
-- for lengths @[1..10]@:
--
-- >     A
-- >    (A, A)
-- >   ((A, A),  A)
-- >   ((A, A), (A, A))
-- >  (((A, A), (A, A)),   A)
-- >  (((A, A), (A, A)),  (A, A))
-- >  (((A, A), (A, A)), ((A, A),  A))
-- >  (((A, A), (A, A)), ((A, A), (A, A)))
-- > ((((A, A), (A, A)), ((A, A), (A, A))),  A)
-- > ((((A, A), (A, A)), ((A, A), (A, A))), (A, A))
nest :: TupleLimit -> [a] -> Forest a
nest mLimit = go . map Leaf
  where
    go :: [Tree a] -> Forest a
    go ts | length ts < limit = Forest ts
          | otherwise         = go (map (Branch . Forest) (chunk limit ts))

    limit :: Int
    limit = case mLimit of
              DefaultGhcTupleLimit -> 62
              MaxTupleElems n      -> n

-- | Maximum number of elements in a tuple
data TupleLimit =
    -- | Default maximum number of elements in a tuple in ghc (62)
    DefaultGhcTupleLimit

    -- | Explicit specified liit
  | MaxTupleElems Int

{-------------------------------------------------------------------------------
  Constructing nested types/values/patterns
-------------------------------------------------------------------------------}

-- | Construct tuple type
mkTupleT :: forall a. (a -> Q Type) -> Forest a -> Q Type
mkTupleT f = forest cata
  where
    cata :: Cata a (Q Type)
    cata = Cata {
          leaf   = f
        , branch = \case [t] -> t
                         ts  -> appsT (tupleT (length ts)) ts
        }

-- | Construct tuple expression
mkTupleE :: forall a. (a -> Q Exp) -> Forest a -> Q Exp
mkTupleE f = forest cata
  where
     cata :: Cata a (Q Exp)
     cata = Cata {
           leaf   = f
         , branch = \case [e] -> e
                          es  -> tupE es
         }

-- | Construct tuple pattern
mkTupleP :: forall a. (a -> Q Pat) -> Forest a -> Q Pat
mkTupleP f = forest cata
  where
    cata :: Cata a (Q Pat)
    cata = Cata {
          leaf   = f
        , branch = \case [p] -> p
                         ps  -> tupP ps
        }

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

chunk :: Int -> [a] -> [[a]]
chunk n = go
  where
    go :: [a] -> [[a]]
    go [] = []
    go xs = let (firstChunk, rest) = splitAt n xs in firstChunk : go rest