{-# LANGUAGE GADTs #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE UndecidableInstances #-} {- | Module : Data.Tuple.Morph Description : Morph between tuples with the same "flattened" representation. Copyright : (c) Paweł Nowak License : MIT Maintainer : Paweł Nowak Stability : provisional Allows you to flatten, unflatten and morph tuples of matching types. Note: by design units are ignored. For example @(Int, (), Char)@ is the same as @(Int, Char)@. -} module Data.Tuple.Morph ( -- * Morphing tuples. morph, sizeLimit, -- * Converting between tuples and HLists. Rep, HFoldable(..), HUnfoldable(..), -- * HList parser. HParser(..), MonoidIndexedMonad(..), ) where import Data.HList.HList (HList(..)) import Data.Proxy import Data.Type.Equality import Data.Tuple.Morph.Append import Data.Tuple.Morph.TH -- | Recurisvely break down a tuple type, representing it as a type list. $(mkRep sizeLimit) -- | Morph a tuple to some isomorphic tuple with the same order of types. -- -- Works with arbitrary nested tuples, each tuple can have size up to 'sizeLimit'. -- -- >>> morph ("a", ("b", "c")) :: (String, String, String) -- ("a","b","c") -- -- >>> morph ((1 :: Int, 2 :: Int), 3 :: Double) :: (Int, (Int, Double)) -- (1,(2,3.0)) -- -- >>> morph ("a", (), (5 :: Int, (), "c")) :: ((), (String, Int), String) -- ((),("a",5),"c") -- -- >>> morph (((("a", "b"), "c"), "d"), "e") :: ((String, String), (String, (String, String))) -- (("a","b"),("c",("d","e"))) morph :: forall a b. (HFoldable a, HUnfoldable b, Rep a ~ Rep b) => a -> b morph = case appendRightId (Proxy :: Proxy (Rep a)) of Refl -> fromHList . toHList -- | Types that can be flattened to a heterogenous list. class HFoldable t where -- | Converts a structure to a heterogenous list. toHList :: t -> HList (Rep t) -- | A function that parses some value @val@ with representation @rep@ -- from a heterogenous list and returns the parsed value and leftovers. newtype HParser (rep :: [*]) val = HParser { -- | Run the parser. runHParser :: forall (leftover :: [*]). HList (rep ++ leftover) -> (val, HList leftover) } -- | An indexed monad on a monoid. class MonoidIndexedMonad (m :: k -> * -> *) where type Empty :: k type Append (x :: k) (y :: k) :: k returnMI :: a -> m Empty a bindMI :: m x a -> (a -> m y b) -> m (Append x y) b instance MonoidIndexedMonad HParser where type Empty = ('[] :: [*]) type Append x y = (x ++ y :: [*]) returnMI a = HParser $ \r -> (a, r) bindMI :: forall (x :: [*]) a (y :: [*]) b. HParser x a -> (a -> HParser y b) -> HParser (Append x y) b bindMI m f = HParser $ g where g :: forall (leftover :: [*]). HList ((Append x y) ++ leftover) -> (b, HList leftover) -- TODO: Explicit type application would be so nice here. g r0 = case appendAssoc (Proxy :: Proxy x) (Proxy :: Proxy y) (Proxy :: Proxy leftover) of Refl -> let (a, r1) = runHParser m r0 (b, r2) = runHParser (f a) r1 in (b, r2) -- | Types that can be built from a heterogenous list. class HUnfoldable t where -- | Build a structure from a heterogenous list. fromHList :: HList (Rep t) -> t fromHList = case appendRightId (Proxy :: Proxy (Rep t)) of Refl -> let parser :: HList (Rep t ++ '[]) -> (t, HList '[]) parser = runHParser hListParser in fst . parser -- | Builds a structure from a heterogenous list and yields the leftovers. hListParser :: HParser (Rep t) t -- HFoldable instances. instance HFoldable () where toHList () = HNil instance (Rep a ~ '[a]) => HFoldable a where toHList a = HCons a HNil $(mapM mkHFoldableInst [2 .. sizeLimit]) -- HUnfoldable instances. instance HUnfoldable () where hListParser = HParser $ \r -> ((), r) instance (Rep a ~ '[a]) => HUnfoldable a where hListParser = HParser $ \(HCons a r) -> (a, r) $(mapM mkHUnfoldableInst [2 .. sizeLimit])