tomland-1.3.3.2: src/Toml/Codec/Di.hs
{- |
Module : Toml.Codec.Di
Copyright : (c) 2018-2022 Kowainik
SPDX-License-Identifier : MPL-2.0
Maintainer : Kowainik <xrom.xkov@gmail.com>
Stability : Stable
Portability : Portable
Forward and backward mapping functions and combinators (similar to profunctors).
@since 1.3.0.0
-}
module Toml.Codec.Di
( dimap
, dioptional
, diwrap
, dimatch
, (.=)
) where
import Control.Applicative (Alternative (..))
import Data.Coerce (Coercible, coerce)
import Toml.Codec.Types (Codec (..), TomlCodec, (<!>))
{- | This is an instance of @Profunctor@ for 'Codec'. But since there's no
@Profunctor@ type class in @base@ or package with no dependencies (and we don't
want to bring extra dependencies) this instance is implemented as a single
top-level function.
Useful when you want to parse @newtype@s. For example, if you had data type like
this:
@
__data__ Example = Example
{ foo :: Bool
, bar :: Text
}
@
Bidirectional TOML converter for this type will look like this:
@
exampleCodec :: TomlCodec Example
exampleCodec = Example
\<$\> Toml.bool "foo" '.=' foo
\<*\> Toml.text "bar" '.=' bar
@
Now if you change your type in the following way:
@
__newtype__ Email = Email { unEmail :: Text }
__data__ Example = Example
{ foo :: Bool
, bar :: Email
}
@
you need to patch your TOML codec like this:
@
exampleCodec :: TomlCodec Example
exampleCodec = Example
\<$\> Toml.bool "foo" '.=' foo
\<*\> 'dimap' unEmail Email (Toml.text "bar") '.=' bar
@
@since 0.2.0
-}
dimap
:: (b -> a) -- ^ Mapper for consumer
-> (a -> b) -- ^ Mapper for producer
-> TomlCodec a -- ^ Source 'Codec' object
-> TomlCodec b -- ^ Target 'Codec' object
dimap f g codec = Codec
{ codecRead = fmap g . codecRead codec
, codecWrite = fmap g . codecWrite codec . f
}
{-# INLINE dimap #-}
{- | Bidirectional converter for @Maybe a@ values. For example, given the data
type:
@
__data__ Example = Example
{ foo :: Bool
, bar :: Maybe Int
}
@
the TOML codec will look like
@
exampleCodec :: TomlCodec Example
exampleCodec = Example
\<$\> Toml.bool "foo" '.=' foo
\<*\> 'dioptional' (Toml.int "bar") '.=' bar
@
@since 0.5.0
-}
dioptional
:: TomlCodec a
-> TomlCodec (Maybe a)
dioptional Codec{..} = Codec
{ codecRead = fmap Just . codecRead <!> \_ -> pure Nothing
, codecWrite = traverse codecWrite
}
{-# INLINE dioptional #-}
{- | Combinator used for @newtype@ wrappers. For example, given the data types:
@
__newtype__ N = N Int
__data__ Example = Example
{ foo :: Bool
, bar :: N
}
@
the TOML codec can look like
@
exampleCodec :: TomlCodec Example
exampleCodec = Example
\<$\> Toml.bool "foo" '.=' foo
\<*\> 'diwrap' (Toml.int "bar") '.=' bar
@
@since 1.0.0
-}
diwrap
:: forall b a .
(Coercible a b)
=> TomlCodec a
-> TomlCodec b
diwrap = coerce
{-# INLINE diwrap #-}
{- | Bidirectional converter for @sum types@. For example, given the data
type:
@
__data__ Example
= Foo Int
| Bar Bool Int
@
the TOML codec will look like
@
matchFoo :: Example -> Maybe Int
matchFoo (Foo num) = Just num
matchFoo _ = Nothing
matchBar :: Example -> Maybe (Bool, Int)
matchBar (Bar b num) = Just (b, num)
matchBar _ = Nothing
barCodec :: TomlCodec (Bool, Int)
barCodec = Toml.pair
(Toml.bool "a")
(Toml.int "b")
exampleCodec :: TomlCodec Example
exampleCodec =
dimatch matchFoo Foo (Toml.int "foo")
\<|\> dimatch matchBar (uncurry Bar) (Toml.table barCodec "bar")
@
@since 1.2.0.0
-}
dimatch
:: (b -> Maybe a) -- ^ Mapper for consumer
-> (a -> b) -- ^ Mapper for producer
-> TomlCodec a -- ^ Source 'Codec' object
-> TomlCodec b -- ^ Target 'Codec' object
dimatch match ctor codec = Codec
{ codecRead = fmap ctor . codecRead codec
, codecWrite = \c -> case match c of
Nothing -> empty
Just d -> ctor <$> codecWrite codec d
}
{-# INLINE dimatch #-}
{- | Operator to connect two operations:
1. How to get field from object?
2. How to write this field to toml?
In code this should be used like this:
@
__data__ Foo = Foo
{ fooBar :: Int
, fooBaz :: String
}
fooCodec :: TomlCodec Foo
fooCodec = Foo
\<$\> Toml.int "bar" '.=' fooBar
\<*\> Toml.str "baz" '.=' fooBaz
@
-}
infixl 5 .=
(.=) :: Codec field a -> (object -> field) -> Codec object a
codec .= getter = codec { codecWrite = codecWrite codec . getter }
{-# INLINE (.=) #-}