{-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -Wall #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Generics.Decode -- Description : Composable decoding of terms -- Copyright : (c) Marco Zocca (2019) -- License : MIT -- Maintainer : ocramz fripost org -- Stability : experimental -- Portability : GHC -- -- Composable decoding of terms ----------------------------------------------------------------------------- module Data.Generics.Decode (Decode, mkDecode, runDecode, (>>>)) where import Control.Applicative (Alternative(..)) import Control.Category (Category(..)) -- import Data.Foldable (Foldable(..), asum) import Control.Monad ((>=>)) -- import Data.Maybe (fromMaybe) -- import Control.Monad.Log (MonadLog(..), Handler, WithSeverity(..), Severity, logDebug, logInfo, logWarning, logError, runLoggingT, PureLoggingT(..), runPureLoggingT) -- import Control.Monad.Trans.State.Strict (StateT(..), runStateT) import Prelude hiding ((.)) -- | We can decouple lookup and value conversion and have distinct error behaviour. -- Multiple value decoding functions can be combined via the Applicative and Alternative instance. -- -- Note : 'Decode' is called Kleisli in base.Control.Arrow; among other things it has a Profunctor instance. newtype Decode m i o = Decode { runDecode_ :: i -> m o } deriving (Functor) -- | Run a decoding function runDecode :: Decode m i o -> i -> m o runDecode = runDecode_ -- | Construct a 'Decode' from a monadic arrow. mkDecode :: (i -> m o) -> Decode m i o mkDecode = Decode instance Applicative m => Applicative (Decode m i) where pure x = Decode $ \ _ -> pure x Decode af <*> Decode aa = Decode $ \ v -> af v <*> aa v instance Alternative m => Alternative (Decode m i) where empty = Decode $ const empty Decode p <|> Decode q = Decode $ \v -> p v <|> q v -- | This instance is copied from @Kleisli@ (defined in Control.Arrow) instance Monad m => Category (Decode m) where id = Decode return (Decode f) . (Decode g) = Decode (g >=> f) -- | Left-to-right composition -- -- @(>>>) :: Monad m => Decode m a b -> Decode m b c -> Decode m a c@ (>>>) :: Category cat => cat a b -> cat b c -> cat a c (>>>) = flip (.) {-# inline (>>>) #-} -- data Spork m a b = Spork (a -> m b) (b -> m a) deriving (Functor) -- | [NOTE Key lookup + value conversion, behaviour of row functions ] -- -- If the key is not found /or/ the conversion fails, use a default; the first exception thrown by the lookup-and-convert function will be rethrown. -- We'd like instead to try many different decoders, and only throw if /all/ have failed -- -- How should Alternative behave for lookup-and-convert that both might fail? -- -- value decoding : try all decoders, return first successful (== Alternative) -- decoding missing values : should be configurable -- -- * use default value -- -- * skip rows that have any missing value -- -- row function: decoding behaviour should be defined for all function arguments -- -- example : -- -- λ> bool (2 :: Int) (VBool False) <|> bool (2 :: Int) (VBool True) -- False -- λ> bool (2 :: Int) (VDouble 32) <|> bool (2 :: Int) (VBool True) -- *** Exception: ValueTypeError 2 VTypeBool (VDouble 32.0) -- -- λ> Nothing <|> pure 32.0 -- Just 32.0 -- λ> (bool (2 :: Int) (VDouble 32) <|> bool (2 :: Int) (VBool True)) :: Maybe Bool -- Just True -- -- ^ throwM in IO : strict (the first failing decoder throws an exception), Maybe : lazy (keeps trying decoders and returns the first successful one)