{-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-} {- | Description : labels which are also lenses A simple problem is being solved here, but unfortunately it is a bit involved. The idea is to use the same haskell identifier for a lens and for other purposes. In other words, get the same behavior as: > x = hLens (Label :: Label "x") > r ^. x While still being able to extract the symbol \"x\" from x, so that things like @x .=. 123@ could be acceptable. In this case we don't overload '.=.', so instead you have to write @x .==. 123@. Elaboration of some ideas from edwardk. -} module Data.HList.Labelable (makeLabelable, Labelable(hLens'), (.==.), -- * comparison with 'hLens' -- $comparisonWithhLensFunction -- * likely unneeded (re)exports -- $note needed to make a needed instance visible Labeled(Labeled), toLabel, Identity, ToSym, ) where import Data.HList.FakePrelude import Data.HList.HArray import Data.HList.HList import Data.HList.Record import Control.Monad.Identity import GHC.TypeLits import Language.Haskell.TH {- | @f s t a b@ type parameters are the same as those that make "Control.Lens" work. [@n@] is the index in the HList at which the value will be found [@l@] is the label for the field (tends to be 'GHC.TypeLits.Symbol') [@p@] is @->@ when the result is used as a lens, or 'Labeled' when used as an argument to '.==.' -} class Labelable l p f s t a b #if MIN_VERSION_base(4,7,0) {- no fundeps in this case: they are potentially inconsistent according to ghc-7.8 <http://ghc.haskell.org/trac/ghc/ticket/2247> these fundeps are mostly documentation, since the two instances have contexts that encode roughly the same dependencies provided you choose a specific `p' -} #else | l s -> a, l t -> b, -- lookup l s b -> t, l t a -> s -- update #endif where hLens' :: Label l -> p (a -> f b) (Record s -> f (Record t)) data Labeled (l :: k) (a :: *) (b :: *) = Labeled deriving (Show) -- | make a lens instance (Functor f, HasField x (Record s) a, HasField x (Record t) b, HFind x (RecordLabels t) n, HFind x (RecordLabels s) n, HUpdateAtHNat n (Tagged x b) s, t ~ HUpdateAtHNatR n (Tagged x b) s) => Labelable x (->) f s t a b where hLens' lab f rec = fmap (\v -> hUpdateAtLabel lab v rec) (f (rec .!. lab)) -- | make a data type that allows recovering the field name instance (f ~ Identity, s ~ '[], t ~ '[], a ~ (), b ~ (), x' ~ x) => Labelable x' (Labeled x) f s t a b where hLens' _ = Labeled :: Labeled x (a -> f b) (Record s -> f (Record t)) -- | modification of '.=.' which works with the labels from this module, -- and those from "Data.HList.Label6". Note that this is not strictly a -- generalization of '.=.', since it does not work with labels like -- "Data.HList.Label3" which have the wrong kind. l .==. v = toLabel l .=. v -- | extracts the type that is actually the label in @a@ and puts it in @b@ class ToSym a b -- | for labels in this module instance (x ~ x', p ~ Labeled x') => ToSym (p a b) x' -- | for "Data.HList.Label6" labels instance (x ~ x') => ToSym (Label x) x' toLabel :: ToSym t t' => t -> Label (t' :: Symbol) toLabel _ = Label {- | @makeLabelable \"x y z\"@ will generate haskell identifiers that work with '.==.' and are also lenses. > x = hLens' (Label :: Label "x") > y = hLens' (Label :: Label "y") > z = hLens' (Label :: Label "z") -} makeLabelable :: String -> Q [Dec] makeLabelable xs = fmap concat $ mapM makeLabel1 (words xs) where -- a bit indirect, ghc-7.6 TH is a bit too eager to reject -- mis-matched kind variables makeLabel1 x = sequence [ sigD (mkName x) makeSig, valD (varP (mkName x)) (normalB (varE 'hLens' `appE` lt)) [] ] where lt = [| Label :: $([t| Label $l |]) |] l = litT (strTyLit x) makeSig = [t| Labelable $l p f s t a b => p (a -> f b) (Record s -> f (Record t)) |] {- $comparisonWithhLensFunction Note that passing around variables defined with 'hLens'' doesn't get you exactly the same thing as calling 'hLens' at the call-site: The following code needs to apply the @x@ for different @Functor f =>@, so you would have to write a type signature (rank-2) to allow this definition: > -- with the x defined using hLens' > let f x r = let > a = r ^. x > b = r & x .~ "6" > in (a,b) This alternative won't need a type signature > -- with the x defined as x = Label :: Label "x" > let f x r = let > a = r ^. hLens x > b = r & hLens x .~ "6" > in (a,b) It may work to use 'hLens'' instead of 'hLens' in the second code, but that is a bit beside the point being made here. -}