generic-data-functions-0.5.1: src/Generic/Data/Function/Traverse/Sum.hs
{-# LANGUAGE UndecidableInstances #-} -- due to type hell
{-# LANGUAGE AllowAmbiguousTypes #-} -- due to generic typeclass design
module Generic.Data.Function.Traverse.Sum where
import GHC.Generics
import Generic.Data.Function.Common.Generic
import Generic.Data.Function.Common.Generic.Meta
import Generic.Data.Function.Traverse.Constructor
import Data.Text ( Text )
import Control.Applicative qualified as Applicative
import Control.Applicative ( Alternative((<|>)) )
-- | Sum type monads that can be generically 'traverse'd.
class GenericTraverse tag => GenericTraverseSum tag where
-- | Try to parse a prefix tag of type 'pt'.
--
-- Relevant metadata is provided as arguments.
genericTraverseSumPfxTagAction
:: GenericTraverseC tag pt
=> String -- ^ data type name
-> GenericTraverseF tag pt
-- | Parse error due to no constructor matching the parsed prefix tag.
--
-- Relevant metadata is provided as arguments.
genericTraverseSumNoMatchingCstrAction
:: String -- ^ data type name
-> [String] -- ^ non-matching constructor names
-> Text -- ^ prefix tag, prettified
-> GenericTraverseF tag a
-- | How to use a type as a prefix tag in a generic sum type parser.
data PfxTagCfg a = PfxTagCfg
{ pfxTagCfgFromCstr :: String -> a
-- ^ How to turn a constructor name into a prefix tag.
, pfxTagCfgEq :: a -> a -> Bool
-- ^ How to compare prefix tags for equality.
--
-- By shoving this into our generic derivation config, we can avoid adding an
-- insidious 'Eq' constraint. In general, you will want to set this to '(==)'.
, pfxTagCfgShow :: a -> Text
-- ^ Make a prefix tag human-readable. 'show' is often appropriate.
}
class GTraverseSum tag gf where
-- have to stuff pt constraint in here because we can't access pt externally
gTraverseSum
:: GenericTraverseC tag pt
=> PfxTagCfg pt -> GenericTraverseF tag (gf p)
instance GenericTraverse tag => GTraverseSum tag V1 where
gTraverseSum _ = genericTraverseV1 @tag
-- | Test all constructors of the given non-void data type; if they all fail,
-- run a failure action and pass it all the constructors names in the type.
instance
( Alternative (GenericTraverseF tag)
, Monad (GenericTraverseF tag)
, GenericTraverseSum tag, GTraverseCSum tag cd gf
, Datatype cd
, KnownSymbols (CstrNames gf)
) => GTraverseSum tag (D1 cd gf) where
gTraverseSum ptc = do
pt <- genericTraverseSumPfxTagAction @tag cd
M1 <$> (gTraverseCSum @tag @cd ptc pt <|> parseErrorNoMatch pt)
where
cd = datatypeName' @cd
parseErrorNoMatch pt =
genericTraverseSumNoMatchingCstrAction @tag cd testedCstrs
((pfxTagCfgShow ptc) pt)
testedCstrs = symbolVals @(CstrNames gf)
class GTraverseCSum tag cd gf where
gTraverseCSum :: PfxTagCfg pt -> pt -> GenericTraverseF tag (gf p)
-- | Combine constructor options with '(<|>)' ("or").
instance
( Alternative (GenericTraverseF tag)
, GTraverseCSum tag cd l
, GTraverseCSum tag cd r
) => GTraverseCSum tag cd (l :+: r) where
gTraverseCSum ptc pt = l <|> r
where
l = L1 <$> gTraverseCSum @tag @cd ptc pt
r = R1 <$> gTraverseCSum @tag @cd ptc pt
-- | If the constructor matches the expected prefix tag, then return the action
-- handling that constructor's contents, else return the empty action.
instance
( Alternative (GenericTraverseF tag)
, GTraverseC tag cd cc 0 gf, Constructor cc
) => GTraverseCSum tag cd (C1 cc gf) where
gTraverseCSum ptc pt = do
if (pfxTagCfgEq ptc) pt ptCstr
then M1 <$> gTraverseC @tag @cd @cc @0
else Applicative.empty
where
ptCstr = (pfxTagCfgFromCstr ptc) (conName' @cc)