{-# LANGUAGE BlockArguments #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module Data.Generic.HKD.Labels ( Label (..) , labelsWhere ) where import Data.Barbie (ProductB (..), TraversableB (..)) import Data.Functor.Const (Const (..)) import Data.Functor.Product (Product (..)) import Data.Generic.HKD.Types (HKD (..), GHKD_) import Data.Kind (Type) import Data.Proxy (Proxy (..)) import GHC.Generics import GHC.TypeLits (ErrorMessage (..), KnownSymbol, TypeError, symbolVal) -- | For any record type, we can extract the labels generically using the -- `Const` functor. -- -- >>> import Data.Generic.HKD -- >>> import Data.Functor.Identity (Identity (..)) -- -- >>> data User = User { name :: String, age :: Int } deriving Generic -- >>> label (deconstruct @Identity (User "Tom" 25)) -- User {name = Const "name", age = Const "age"} class Label (structure :: Type) where label :: HKD structure f -> HKD structure (Const String) class GLabels (rep :: Type -> Type) where glabel :: GHKD_ f rep p -> GHKD_ (Const String) rep p instance GLabels inner => GLabels (D1 meta inner) where glabel = M1 . glabel . unM1 instance GLabels inner => GLabels (C1 ('MetaCons name fixity 'True) inner) where glabel = M1 . glabel . unM1 instance TypeError ('Text "You can't collect labels for a non-record type!") => GLabels (C1 ('MetaCons name fixity 'False) inner) where glabel = undefined instance KnownSymbol name => GLabels (S1 ('MetaSel ('Just name) i d c) (K1 index inner)) where glabel _ = M1 (K1 (Const (symbolVal (Proxy @name)))) instance (GLabels left, GLabels right) => GLabels (left :*: right) where glabel (left :*: right) = glabel left :*: glabel right instance (Generic structure, GLabels (Rep structure)) => Label structure where label = HKD . glabel . runHKD -- | Because all HKD types are valid barbies, and we have the above mechanism -- for extracting field names, we can ask some pretty interesting questions. -- -- >>> import Control.Lens -- >>> import Data.Maybe (isNothing) -- >>> import Data.Monoid (Last (..)) -- >>> import Data.Generic.HKD -- -- Let's imagine, for example, that we're half way through filling in a user's -- details: -- -- >>> data User = User { name :: String, age :: Int } deriving Generic -- >>> test = mempty @(HKD User Last) & field @"name" .~ pure "Tom" -- -- We want to send a JSON response back to the client containing the fields -- that have yet to be finished. All we need to do is pick the fields where the -- values are @Last Nothing@: -- -- >>> labelsWhere (isNothing . getLast) test -- ["age"] labelsWhere :: forall structure f . ( Label structure , ProductB (HKD structure) , TraversableB (HKD structure) ) => (forall a. f a -> Bool) -> HKD structure f -> [String] labelsWhere p xs = getConst (btraverse go (label xs `bprod` xs)) where go :: Product (Const String) f a -> (Const [String]) (Maybe a) go (Pair (Const key) value) = Const if p value then [key] else []