{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-| Please read the "Dhall.Tutorial" module, which contains a tutorial explaining how to use the language, the compiler, and this library -} module Dhall.Marshal.Encode ( -- * General Encoder(..) , ToDhall(..) , Inject , inject -- * Building encoders -- ** Records , RecordEncoder(..) , recordEncoder , encodeField , encodeFieldWith -- ** Unions , UnionEncoder(..) , unionEncoder , encodeConstructor , encodeConstructorWith , (>|<) -- * Generic encoding , GenericToDhall(..) , genericToDhall , genericToDhallWith , InterpretOptions(..) , SingletonConstructors(..) , defaultInterpretOptions -- * Miscellaneous , InputNormalizer(..) , defaultInputNormalizer , Result , (>$<) , (>*<) -- * Re-exports , Natural , Seq , Text , Vector , Generic ) where import Control.Monad.Trans.State.Strict import Data.Functor.Contravariant (Contravariant (..), Op (..), (>$<)) import Data.Functor.Contravariant.Divisible (Divisible (..), divided) import Dhall.Parser (Src (..)) import Dhall.Syntax ( Chunks (..) , DhallDouble (..) , Expr (..) ) import GHC.Generics import Prelude hiding (maybe, sequence) import qualified Control.Applicative import qualified Data.Functor.Product import qualified Data.HashMap.Strict as HashMap import qualified Data.HashSet import qualified Data.Map import qualified Data.Scientific import qualified Data.Sequence import qualified Data.Set import qualified Data.Text import qualified Data.Text.Lazy import qualified Data.Vector import qualified Data.Void import qualified Dhall.Core as Core import qualified Dhall.Map import Dhall.Marshal.Internal -- $setup -- >>> :set -XRecordWildCards -- >>> import Dhall.Pretty.Internal (prettyExpr) {-| An @(Encoder a)@ represents a way to marshal a value of type @\'a\'@ from Haskell into Dhall. -} data Encoder a = Encoder { embed :: a -> Expr Src Void -- ^ Embeds a Haskell value as a Dhall expression , declared :: Expr Src Void -- ^ Dhall type of the Haskell value } instance Contravariant Encoder where contramap f (Encoder embed declared) = Encoder embed' declared where embed' x = embed (f x) {-| This class is used by `Dhall.Marshal.Decode.FromDhall` instance for functions: > instance (ToDhall a, FromDhall b) => FromDhall (a -> b) You can convert Dhall functions with "simple" inputs (i.e. instances of this class) into Haskell functions. This works by: * Marshaling the input to the Haskell function into a Dhall expression (i.e. @x :: Expr Src Void@) * Applying the Dhall function (i.e. @f :: Expr Src Void@) to the Dhall input (i.e. @App f x@) * Normalizing the syntax tree (i.e. @normalize (App f x)@) * Marshaling the resulting Dhall expression back into a Haskell value This class auto-generates a default implementation for types that implement `Generic`. This does not auto-generate an instance for recursive types. The default instance can be tweaked using 'genericToDhallWith' and custom 'InterpretOptions', or using [DerivingVia](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extension-DerivingVia) and 'Dhall.Deriving.Codec' from "Dhall.Deriving". -} class ToDhall a where injectWith :: InputNormalizer -> Encoder a default injectWith :: (Generic a, GenericToDhall (Rep a)) => InputNormalizer -> Encoder a injectWith _ = genericToDhall -- | A compatibility alias for `ToDhall` type Inject = ToDhall {-# DEPRECATED Inject "Use ToDhall instead" #-} {-| Use the default input normalizer for injecting a value. > inject = injectWith defaultInputNormalizer -} inject :: ToDhall a => Encoder a inject = injectWith defaultInputNormalizer instance ToDhall Void where injectWith _ = Encoder {..} where embed = Data.Void.absurd declared = Union mempty instance ToDhall Bool where injectWith _ = Encoder {..} where embed = BoolLit declared = Bool instance ToDhall Data.Text.Lazy.Text where injectWith _ = Encoder {..} where embed text = TextLit (Chunks [] (Data.Text.Lazy.toStrict text)) declared = Text instance ToDhall Text where injectWith _ = Encoder {..} where embed text = TextLit (Chunks [] text) declared = Text instance {-# OVERLAPS #-} ToDhall String where injectWith inputNormalizer = contramap Data.Text.pack (injectWith inputNormalizer :: Encoder Text) instance ToDhall Natural where injectWith _ = Encoder {..} where embed = NaturalLit declared = Natural instance ToDhall Integer where injectWith _ = Encoder {..} where embed = IntegerLit declared = Integer instance ToDhall Int where injectWith _ = Encoder {..} where embed = IntegerLit . toInteger declared = Integer {-| Encode a 'Word' to a Dhall @Natural@. >>> embed inject (12 :: Word) NaturalLit 12 -} instance ToDhall Word where injectWith _ = Encoder {..} where embed = NaturalLit . fromIntegral declared = Natural {-| Encode a 'Word8' to a Dhall @Natural@. >>> embed inject (12 :: Word8) NaturalLit 12 -} instance ToDhall Word8 where injectWith _ = Encoder {..} where embed = NaturalLit . fromIntegral declared = Natural {-| Encode a 'Word16' to a Dhall @Natural@. >>> embed inject (12 :: Word16) NaturalLit 12 -} instance ToDhall Word16 where injectWith _ = Encoder {..} where embed = NaturalLit . fromIntegral declared = Natural {-| Encode a 'Word32' to a Dhall @Natural@. >>> embed inject (12 :: Word32) NaturalLit 12 -} instance ToDhall Word32 where injectWith _ = Encoder {..} where embed = NaturalLit . fromIntegral declared = Natural {-| Encode a 'Word64' to a Dhall @Natural@. >>> embed inject (12 :: Word64) NaturalLit 12 -} instance ToDhall Word64 where injectWith _ = Encoder {..} where embed = NaturalLit . fromIntegral declared = Natural instance ToDhall Double where injectWith _ = Encoder {..} where embed = DoubleLit . DhallDouble declared = Double instance ToDhall Scientific where injectWith inputNormalizer = contramap Data.Scientific.toRealFloat (injectWith inputNormalizer :: Encoder Double) instance ToDhall () where injectWith _ = Encoder {..} where embed = const (RecordLit mempty) declared = Record mempty instance ToDhall a => ToDhall (Maybe a) where injectWith inputNormalizer = Encoder embedOut declaredOut where embedOut (Just x ) = Some (embedIn x) embedOut Nothing = App None declaredIn Encoder embedIn declaredIn = injectWith inputNormalizer declaredOut = App Optional declaredIn instance ToDhall a => ToDhall (Seq a) where injectWith inputNormalizer = Encoder embedOut declaredOut where embedOut xs = ListLit listType (fmap embedIn xs) where listType | null xs = Just (App List declaredIn) | otherwise = Nothing declaredOut = App List declaredIn Encoder embedIn declaredIn = injectWith inputNormalizer instance ToDhall a => ToDhall [a] where injectWith = fmap (contramap Data.Sequence.fromList) injectWith instance ToDhall a => ToDhall (Vector a) where injectWith = fmap (contramap Data.Vector.toList) injectWith {-| Note that the output list will be sorted. >>> let x = Data.Set.fromList ["mom", "hi" :: Text] >>> prettyExpr $ embed inject x [ "hi", "mom" ] -} instance ToDhall a => ToDhall (Data.Set.Set a) where injectWith = fmap (contramap Data.Set.toAscList) injectWith -- | Note that the output list may not be sorted instance ToDhall a => ToDhall (Data.HashSet.HashSet a) where injectWith = fmap (contramap Data.HashSet.toList) injectWith instance (ToDhall a, ToDhall b) => ToDhall (a, b) {-| Embed a `Data.Map` as a @Prelude.Map.Type@. >>> prettyExpr $ embed inject (Data.Map.fromList [(1 :: Natural, True)]) [ { mapKey = 1, mapValue = True } ] >>> prettyExpr $ embed inject (Data.Map.fromList [] :: Data.Map.Map Natural Bool) [] : List { mapKey : Natural, mapValue : Bool } -} instance (ToDhall k, ToDhall v) => ToDhall (Data.Map.Map k v) where injectWith inputNormalizer = Encoder embedOut declaredOut where embedOut m = ListLit listType (mapEntries m) where listType | Data.Map.null m = Just declaredOut | otherwise = Nothing declaredOut = App List (Record $ Dhall.Map.fromList [ ("mapKey", Core.makeRecordField declaredK) , ("mapValue", Core.makeRecordField declaredV) ]) mapEntries = Data.Sequence.fromList . fmap recordPair . Data.Map.toList recordPair (k, v) = RecordLit $ Dhall.Map.fromList [ ("mapKey", Core.makeRecordField $ embedK k) , ("mapValue", Core.makeRecordField $ embedV v) ] Encoder embedK declaredK = injectWith inputNormalizer Encoder embedV declaredV = injectWith inputNormalizer {-| Embed a `Data.HashMap` as a @Prelude.Map.Type@. >>> prettyExpr $ embed inject (HashMap.fromList [(1 :: Natural, True)]) [ { mapKey = 1, mapValue = True } ] >>> prettyExpr $ embed inject (HashMap.fromList [] :: HashMap Natural Bool) [] : List { mapKey : Natural, mapValue : Bool } -} instance (ToDhall k, ToDhall v) => ToDhall (HashMap k v) where injectWith inputNormalizer = Encoder embedOut declaredOut where embedOut m = ListLit listType (mapEntries m) where listType | HashMap.null m = Just declaredOut | otherwise = Nothing declaredOut = App List (Record $ Dhall.Map.fromList [ ("mapKey", Core.makeRecordField declaredK) , ("mapValue", Core.makeRecordField declaredV) ]) mapEntries = Data.Sequence.fromList . fmap recordPair . HashMap.toList recordPair (k, v) = RecordLit $ Dhall.Map.fromList [ ("mapKey", Core.makeRecordField $ embedK k) , ("mapValue", Core.makeRecordField $ embedV v) ] Encoder embedK declaredK = injectWith inputNormalizer Encoder embedV declaredV = injectWith inputNormalizer instance ToDhall (f (Result f)) => ToDhall (Result f) where injectWith inputNormalizer = Encoder {..} where embed = App "Make" . Dhall.Marshal.Encode.embed (injectWith inputNormalizer) . _unResult declared = "result" instance forall f. (Functor f, ToDhall (f (Result f))) => ToDhall (Fix f) where injectWith inputNormalizer = Encoder {..} where embed fixf = Lam Nothing (Core.makeFunctionBinding "result" (Const Core.Type)) $ Lam Nothing (Core.makeFunctionBinding "Make" makeType) $ embed' . fixToResult $ fixf declared = Pi Nothing "result" (Const Core.Type) $ Pi Nothing "_" makeType "result" makeType = Pi Nothing "_" declared' "result" Encoder embed' _ = injectWith @(Dhall.Marshal.Internal.Result f) inputNormalizer Encoder _ declared' = injectWith @(f (Dhall.Marshal.Internal.Result f)) inputNormalizer fixToResult :: Functor f => Fix f -> Result f fixToResult (Fix x) = Result (fmap fixToResult x) {-| This is the underlying class that powers the `Dhall.Marshal.Decode.FromDhall` class's support for automatically deriving a generic implementation. -} class GenericToDhall f where genericToDhallWithNormalizer :: InputNormalizer -> InterpretOptions -> State Int (Encoder (f a)) instance GenericToDhall f => GenericToDhall (M1 D d f) where genericToDhallWithNormalizer inputNormalizer options = do res <- genericToDhallWithNormalizer inputNormalizer options pure (contramap unM1 res) instance GenericToDhall f => GenericToDhall (M1 C c f) where genericToDhallWithNormalizer inputNormalizer options = do res <- genericToDhallWithNormalizer inputNormalizer options pure (contramap unM1 res) instance (Selector s, ToDhall a) => GenericToDhall (M1 S s (K1 i a)) where genericToDhallWithNormalizer inputNormalizer InterpretOptions{..} = do let Encoder { embed = embed', declared = declared' } = injectWith inputNormalizer let n :: M1 S s (K1 i a) r n = undefined name <- fieldModifier <$> getSelName n let embed0 (M1 (K1 x)) = embed' x let embed1 (M1 (K1 x)) = RecordLit (Dhall.Map.singleton name (Core.makeRecordField $ embed' x)) let embed = case singletonConstructors of Bare -> embed0 Smart | selName n == "" -> embed0 _ -> embed1 let declared = case singletonConstructors of Bare -> declared' Smart | selName n == "" -> declared' _ -> Record (Dhall.Map.singleton name $ Core.makeRecordField declared') return (Encoder {..}) instance (Constructor c1, Constructor c2, GenericToDhall f1, GenericToDhall f2) => GenericToDhall (M1 C c1 f1 :+: M1 C c2 f2) where genericToDhallWithNormalizer inputNormalizer options@(InterpretOptions {..}) = pure (Encoder {..}) where embed (L1 (M1 l)) = case notEmptyRecordLit (embedL l) of Nothing -> Field declared $ Core.makeFieldSelection keyL Just valL -> App (Field declared $ Core.makeFieldSelection keyL) valL embed (R1 (M1 r)) = case notEmptyRecordLit (embedR r) of Nothing -> Field declared $ Core.makeFieldSelection keyR Just valR -> App (Field declared $ Core.makeFieldSelection keyR) valR declared = Union (Dhall.Map.fromList [ (keyL, notEmptyRecord declaredL) , (keyR, notEmptyRecord declaredR) ] ) nL :: M1 i c1 f1 a nL = undefined nR :: M1 i c2 f2 a nR = undefined keyL = constructorModifier (Data.Text.pack (conName nL)) keyR = constructorModifier (Data.Text.pack (conName nR)) Encoder embedL declaredL = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 Encoder embedR declaredR = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 instance (Constructor c, GenericToDhall (f :+: g), GenericToDhall h) => GenericToDhall ((f :+: g) :+: M1 C c h) where genericToDhallWithNormalizer inputNormalizer options@(InterpretOptions {..}) = pure (Encoder {..}) where embed (L1 l) = case maybeValL of Nothing -> Field declared $ Core.makeFieldSelection keyL Just valL -> App (Field declared $ Core.makeFieldSelection keyL) valL where (keyL, maybeValL) = unsafeExpectUnionLit "genericToDhallWithNormalizer (:+:)" (embedL l) embed (R1 (M1 r)) = case notEmptyRecordLit (embedR r) of Nothing -> Field declared $ Core.makeFieldSelection keyR Just valR -> App (Field declared $ Core.makeFieldSelection keyR) valR nR :: M1 i c h a nR = undefined keyR = constructorModifier (Data.Text.pack (conName nR)) declared = Union (Dhall.Map.insert keyR (notEmptyRecord declaredR) ktsL) Encoder embedL declaredL = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 Encoder embedR declaredR = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 ktsL = unsafeExpectUnion "genericToDhallWithNormalizer (:+:)" declaredL instance (Constructor c, GenericToDhall f, GenericToDhall (g :+: h)) => GenericToDhall (M1 C c f :+: (g :+: h)) where genericToDhallWithNormalizer inputNormalizer options@(InterpretOptions {..}) = pure (Encoder {..}) where embed (L1 (M1 l)) = case notEmptyRecordLit (embedL l) of Nothing -> Field declared $ Core.makeFieldSelection keyL Just valL -> App (Field declared $ Core.makeFieldSelection keyL) valL embed (R1 r) = case maybeValR of Nothing -> Field declared $ Core.makeFieldSelection keyR Just valR -> App (Field declared $ Core.makeFieldSelection keyR) valR where (keyR, maybeValR) = unsafeExpectUnionLit "genericToDhallWithNormalizer (:+:)" (embedR r) nL :: M1 i c f a nL = undefined keyL = constructorModifier (Data.Text.pack (conName nL)) declared = Union (Dhall.Map.insert keyL (notEmptyRecord declaredL) ktsR) Encoder embedL declaredL = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 Encoder embedR declaredR = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 ktsR = unsafeExpectUnion "genericToDhallWithNormalizer (:+:)" declaredR instance (GenericToDhall (f :+: g), GenericToDhall (h :+: i)) => GenericToDhall ((f :+: g) :+: (h :+: i)) where genericToDhallWithNormalizer inputNormalizer options = pure (Encoder {..}) where embed (L1 l) = case maybeValL of Nothing -> Field declared $ Core.makeFieldSelection keyL Just valL -> App (Field declared $ Core.makeFieldSelection keyL) valL where (keyL, maybeValL) = unsafeExpectUnionLit "genericToDhallWithNormalizer (:+:)" (embedL l) embed (R1 r) = case maybeValR of Nothing -> Field declared $ Core.makeFieldSelection keyR Just valR -> App (Field declared $ Core.makeFieldSelection keyR) valR where (keyR, maybeValR) = unsafeExpectUnionLit "genericToDhallWithNormalizer (:+:)" (embedR r) declared = Union (Dhall.Map.union ktsL ktsR) Encoder embedL declaredL = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 Encoder embedR declaredR = evalState (genericToDhallWithNormalizer inputNormalizer options) 1 ktsL = unsafeExpectUnion "genericToDhallWithNormalizer (:+:)" declaredL ktsR = unsafeExpectUnion "genericToDhallWithNormalizer (:+:)" declaredR instance (GenericToDhall (f :*: g), GenericToDhall (h :*: i)) => GenericToDhall ((f :*: g) :*: (h :*: i)) where genericToDhallWithNormalizer inputNormalizer options = do Encoder embedL declaredL <- genericToDhallWithNormalizer inputNormalizer options Encoder embedR declaredR <- genericToDhallWithNormalizer inputNormalizer options let embed (l :*: r) = RecordLit (Dhall.Map.union mapL mapR) where mapL = unsafeExpectRecordLit "genericToDhallWithNormalizer (:*:)" (embedL l) mapR = unsafeExpectRecordLit "genericToDhallWithNormalizer (:*:)" (embedR r) let declared = Record (Dhall.Map.union mapL mapR) where mapL = unsafeExpectRecord "genericToDhallWithNormalizer (:*:)" declaredL mapR = unsafeExpectRecord "genericToDhallWithNormalizer (:*:)" declaredR pure (Encoder {..}) instance (GenericToDhall (f :*: g), Selector s, ToDhall a) => GenericToDhall ((f :*: g) :*: M1 S s (K1 i a)) where genericToDhallWithNormalizer inputNormalizer options@InterpretOptions{..} = do let nR :: M1 S s (K1 i a) r nR = undefined nameR <- fmap fieldModifier (getSelName nR) Encoder embedL declaredL <- genericToDhallWithNormalizer inputNormalizer options let Encoder embedR declaredR = injectWith inputNormalizer let embed (l :*: M1 (K1 r)) = RecordLit (Dhall.Map.insert nameR (Core.makeRecordField $ embedR r) mapL) where mapL = unsafeExpectRecordLit "genericToDhallWithNormalizer (:*:)" (embedL l) let declared = Record (Dhall.Map.insert nameR (Core.makeRecordField declaredR) mapL) where mapL = unsafeExpectRecord "genericToDhallWithNormalizer (:*:)" declaredL return (Encoder {..}) instance (Selector s, ToDhall a, GenericToDhall (f :*: g)) => GenericToDhall (M1 S s (K1 i a) :*: (f :*: g)) where genericToDhallWithNormalizer inputNormalizer options@InterpretOptions{..} = do let nL :: M1 S s (K1 i a) r nL = undefined nameL <- fmap fieldModifier (getSelName nL) let Encoder embedL declaredL = injectWith inputNormalizer Encoder embedR declaredR <- genericToDhallWithNormalizer inputNormalizer options let embed (M1 (K1 l) :*: r) = RecordLit (Dhall.Map.insert nameL (Core.makeRecordField $ embedL l) mapR) where mapR = unsafeExpectRecordLit "genericToDhallWithNormalizer (:*:)" (embedR r) let declared = Record (Dhall.Map.insert nameL (Core.makeRecordField declaredL) mapR) where mapR = unsafeExpectRecord "genericToDhallWithNormalizer (:*:)" declaredR return (Encoder {..}) instance (Selector s1, Selector s2, ToDhall a1, ToDhall a2) => GenericToDhall (M1 S s1 (K1 i1 a1) :*: M1 S s2 (K1 i2 a2)) where genericToDhallWithNormalizer inputNormalizer InterpretOptions{..} = do let nL :: M1 S s1 (K1 i1 a1) r nL = undefined let nR :: M1 S s2 (K1 i2 a2) r nR = undefined nameL <- fmap fieldModifier (getSelName nL) nameR <- fmap fieldModifier (getSelName nR) let Encoder embedL declaredL = injectWith inputNormalizer let Encoder embedR declaredR = injectWith inputNormalizer let embed (M1 (K1 l) :*: M1 (K1 r)) = RecordLit $ Dhall.Map.fromList [ (nameL, Core.makeRecordField $ embedL l) , (nameR, Core.makeRecordField $ embedR r) ] let declared = Record $ Dhall.Map.fromList [ (nameL, Core.makeRecordField declaredL) , (nameR, Core.makeRecordField declaredR) ] return (Encoder {..}) instance GenericToDhall U1 where genericToDhallWithNormalizer _ _ = pure (Encoder {..}) where embed _ = RecordLit mempty declared = Record mempty {-| Use the default options for injecting a value, whose structure is determined generically. This can be used when you want to use 'ToDhall' on types that you don't want to define orphan instances for. -} genericToDhall :: (Generic a, GenericToDhall (Rep a)) => Encoder a genericToDhall = genericToDhallWith defaultInterpretOptions {-| Use custom options for injecting a value, whose structure is determined generically. This can be used when you want to use 'ToDhall' on types that you don't want to define orphan instances for. -} genericToDhallWith :: (Generic a, GenericToDhall (Rep a)) => InterpretOptions -> Encoder a genericToDhallWith options = contramap GHC.Generics.from (evalState (genericToDhallWithNormalizer defaultInputNormalizer options) 1) {-| The 'RecordEncoder' divisible (contravariant) functor allows you to build an 'Encoder' for a Dhall record. For example, let's take the following Haskell data type: >>> :{ data Project = Project { projectName :: Text , projectDescription :: Text , projectStars :: Natural } :} And assume that we have the following Dhall record that we would like to parse as a @Project@: > { name = > "dhall-haskell" > , description = > "A configuration language guaranteed to terminate" > , stars = > 289 > } Our encoder has type 'Encoder' @Project@, but we can't build that out of any smaller encoders, as 'Encoder's cannot be combined (they are only 'Contravariant's). However, we can use an 'RecordEncoder' to build an 'Encoder' for @Project@: >>> :{ injectProject :: Encoder Project injectProject = recordEncoder ( adapt >$< encodeFieldWith "name" inject >*< encodeFieldWith "description" inject >*< encodeFieldWith "stars" inject ) where adapt (Project{..}) = (projectName, (projectDescription, projectStars)) :} Or, since we are simply using the `ToDhall` instance to inject each field, we could write >>> :{ injectProject :: Encoder Project injectProject = recordEncoder ( adapt >$< encodeField "name" >*< encodeField "description" >*< encodeField "stars" ) where adapt (Project{..}) = (projectName, (projectDescription, projectStars)) :} -} newtype RecordEncoder a = RecordEncoder (Dhall.Map.Map Text (Encoder a)) instance Contravariant RecordEncoder where contramap f (RecordEncoder encodeTypeRecord) = RecordEncoder $ contramap f <$> encodeTypeRecord instance Divisible RecordEncoder where divide f (RecordEncoder bEncoderRecord) (RecordEncoder cEncoderRecord) = RecordEncoder $ Dhall.Map.union ((contramap $ fst . f) <$> bEncoderRecord) ((contramap $ snd . f) <$> cEncoderRecord) conquer = RecordEncoder mempty -- | Convert a `RecordEncoder` into the equivalent `Encoder`. recordEncoder :: RecordEncoder a -> Encoder a recordEncoder (RecordEncoder encodeTypeRecord) = Encoder makeRecordLit recordType where recordType = Record $ (Core.makeRecordField . declared) <$> encodeTypeRecord makeRecordLit x = RecordLit $ (Core.makeRecordField . ($ x) . embed) <$> encodeTypeRecord {-| Specify how to encode one field of a record using the default `ToDhall` instance for that type. -} encodeField :: ToDhall a => Text -> RecordEncoder a encodeField name = encodeFieldWith name inject {-| Specify how to encode one field of a record by supplying an explicit `Encoder` for that field. -} encodeFieldWith :: Text -> Encoder a -> RecordEncoder a encodeFieldWith name encodeType = RecordEncoder $ Dhall.Map.singleton name encodeType {-| 'UnionEncoder' allows you to build an 'Encoder' for a Dhall record. For example, let's take the following Haskell data type: >>> :{ data Status = Queued Natural | Result Text | Errored Text :} And assume that we have the following Dhall union that we would like to parse as a @Status@: > < Result : Text > | Queued : Natural > | Errored : Text > >.Result "Finish successfully" Our encoder has type 'Encoder' @Status@, but we can't build that out of any smaller encoders, as 'Encoder's cannot be combined. However, we can use an 'UnionEncoder' to build an 'Encoder' for @Status@: >>> :{ injectStatus :: Encoder Status injectStatus = adapt >$< unionEncoder ( encodeConstructorWith "Queued" inject >|< encodeConstructorWith "Result" inject >|< encodeConstructorWith "Errored" inject ) where adapt (Queued n) = Left n adapt (Result t) = Right (Left t) adapt (Errored e) = Right (Right e) :} Or, since we are simply using the `ToDhall` instance to inject each branch, we could write >>> :{ injectStatus :: Encoder Status injectStatus = adapt >$< unionEncoder ( encodeConstructor "Queued" >|< encodeConstructor "Result" >|< encodeConstructor "Errored" ) where adapt (Queued n) = Left n adapt (Result t) = Right (Left t) adapt (Errored e) = Right (Right e) :} -} newtype UnionEncoder a = UnionEncoder ( Data.Functor.Product.Product ( Control.Applicative.Const ( Dhall.Map.Map Text ( Expr Src Void ) ) ) ( Op (Text, Expr Src Void) ) a ) deriving (Contravariant) -- | Convert a `UnionEncoder` into the equivalent `Encoder`. unionEncoder :: UnionEncoder a -> Encoder a unionEncoder ( UnionEncoder ( Data.Functor.Product.Pair ( Control.Applicative.Const fields ) ( Op embedF ) ) ) = Encoder { embed = \x -> let (name, y) = embedF x in case notEmptyRecordLit y of Nothing -> Field (Union fields') $ Core.makeFieldSelection name Just val -> App (Field (Union fields') $ Core.makeFieldSelection name) val , declared = Union fields' } where fields' = fmap notEmptyRecord fields {-| Specify how to encode an alternative by using the default `ToDhall` instance for that type. -} encodeConstructor :: ToDhall a => Text -> UnionEncoder a encodeConstructor name = encodeConstructorWith name inject {-| Specify how to encode an alternative by providing an explicit `Encoder` for that alternative. -} encodeConstructorWith :: Text -> Encoder a -> UnionEncoder a encodeConstructorWith name encodeType = UnionEncoder $ Data.Functor.Product.Pair ( Control.Applicative.Const ( Dhall.Map.singleton name ( declared encodeType ) ) ) ( Op ( (name,) . embed encodeType ) ) -- | Combines two 'UnionEncoder' values. See 'UnionEncoder' for usage -- notes. -- -- Ideally, this matches 'Data.Functor.Contravariant.Divisible.chosen'; -- however, this allows 'UnionEncoder' to not need a 'Divisible' instance -- itself (since no instance is possible). (>|<) :: UnionEncoder a -> UnionEncoder b -> UnionEncoder (Either a b) UnionEncoder (Data.Functor.Product.Pair (Control.Applicative.Const mx) (Op fx)) >|< UnionEncoder (Data.Functor.Product.Pair (Control.Applicative.Const my) (Op fy)) = UnionEncoder ( Data.Functor.Product.Pair ( Control.Applicative.Const (mx <> my) ) ( Op (either fx fy) ) ) infixr 5 >|< -- | Infix 'divided' (>*<) :: Divisible f => f a -> f b -> f (a, b) (>*<) = divided infixr 5 >*<