harg-0.1.0.0: src/Options/Harg/Het/Prod.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Options.Harg.Het.Prod where
import Data.Functor.Identity (Identity)
import Data.Kind (Type)
import Data.Proxy (Proxy(..))
import GHC.Generics (Generic)
import GHC.TypeLits (KnownSymbol, symbolVal)
import Data.Aeson ((.:?), (.!=))
import qualified Data.Aeson as JSON
import qualified Data.Barbie as B
import qualified Data.Text as Tx
-- | Infix version of 'Data.Functor.Product'. Allows to combine
-- higher-kinded types, and keep them partially applied until needed:
--
-- @
-- data User = User { name :: String, age :: Int }
-- deriving Generic
--
-- type Config = Nested User :* Single Int
--
-- configOpt :: Config Opt
-- configOpt = ...
-- @
--
data
((a :: (Type -> Type) -> Type) :* (b :: (Type -> Type) -> Type))
(f :: Type -> Type)
= a f :* b f
deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)
infixr 4 :*
deriving instance
( Show (a Identity)
, Show (b Identity)
) => Show ((a :* b) Identity)
-- | This type adds a type-level phantom tag to a higher-kinded type.
-- Its JSON instance allows using ':*' with 'Options.Harg.Sources.JSON.JSONSource'.
newtype Tagged
(t :: k)
(a :: (Type -> Type) -> Type)
(f :: Type -> Type)
= Tagged
{ unTagged :: a f
}
deriving (Generic)
deriving newtype instance JSON.FromJSON (a f) => JSON.FromJSON (Tagged t a f)
instance B.FunctorB a => B.FunctorB (Tagged t a) where
bmap nat (Tagged x) = Tagged (B.bmap nat x)
instance B.TraversableB a => B.TraversableB (Tagged t a) where
btraverse nat (Tagged x) = Tagged <$> B.btraverse nat x
instance B.ProductB a => B.ProductB (Tagged t a) where
bprod (Tagged l) (Tagged r) = Tagged (B.bprod l r)
buniq f = Tagged (B.buniq f)
-- The following JSON instances need to work if and only if all elements in
-- the product are `Tagged`, hence the weird pattern matches
instance ( JSON.FromJSON (a Maybe)
, JSON.FromJSON (b' Maybe)
, B.ProductB a, B.ProductB b'
, KnownSymbol ta
, b' ~ (Tagged tb b :* c)
) => JSON.FromJSON ((Tagged ta a :* (Tagged tb b :* c)) Maybe) where
parseJSON
= JSON.withObject ":*"
$ \o ->
(:*)
<$> o .:? Tx.pack (symbolVal (Proxy :: Proxy ta)) .!= B.buniq Nothing
<*> JSON.parseJSON (JSON.Object o)
instance
( JSON.FromJSON (a Maybe)
, JSON.FromJSON (b Maybe)
, B.ProductB a, B.ProductB b
, KnownSymbol ta
, KnownSymbol tb
) => JSON.FromJSON ((Tagged ta a :* Tagged tb b) Maybe) where
parseJSON
= JSON.withObject ":*"
$ \o ->
(:*)
<$> o .:? Tx.pack (symbolVal (Proxy :: Proxy ta)) .!= B.buniq Nothing
<*> o .:? Tx.pack (symbolVal (Proxy :: Proxy tb)) .!= B.buniq Nothing