{-# LANGUAGE DuplicateRecordFields, NoFieldSelectors, OverloadedRecordDot, DeriveGeneric, OverloadedStrings, DataKinds, TypeApplications, DefaultSignatures #-} import Data.Proxy import Data.Text (Text) import qualified Data.Text as T import GHC.Generics import GHC.TypeLits data MyCollectorA = MyCollectorA { a :: Maybe Int , b :: Maybe Text , c :: Maybe Int -- … } deriving (Show, Eq, Generic) data MyCollectorB = MyCollectorB { a :: Maybe Int , b :: Maybe Text , c :: Maybe Int -- … } deriving (Show, Eq, Generic) class OptionalFields' f where optionalFields' :: f p -> [Text] class OptionalFields a where optionalFields :: a -> [Text] default optionalFields :: (Generic a, OptionalFields' (Rep a)) => a -> [Text] optionalFields = optionalFields' . from instance (OptionalFields' l, OptionalFields' r) => OptionalFields' (l :*: r) where optionalFields' (l :*: r) = optionalFields' l ++ optionalFields' r instance (OptionalFields c) => OptionalFields' (K1 i c) where optionalFields' (K1 x) = optionalFields x instance KnownSymbol s => OptionalFields' (M1 S (MetaSel (Just s) su ss ds) (Rec0 (Maybe a))) where optionalFields' (M1 (K1 x)) = case x of Nothing -> [T.pack $ symbolVal $ Proxy @s] Just{} -> [] instance (OptionalFields' x) => OptionalFields' (M1 C t x) where optionalFields' (M1 x) = optionalFields' x instance (OptionalFields' x) => OptionalFields' (M1 D t x) where optionalFields' (M1 x) = optionalFields' x instance OptionalFields MyCollectorA instance OptionalFields MyCollectorB -- | Function that determines which records are missing. -- -- >>> missingEntities (MyCollectorA (Just 1) Nothing (Just 3)) -- ["b"] missingEntities :: OptionalFields a => a -> [Text] missingEntities = optionalFields main = do print $ missingEntities (MyCollectorA (Just 1) Nothing (Just 3)) print $ missingEntities (MyCollectorB Nothing Nothing Nothing)