{-# 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