fclabels 0.4.2.1 → 0.9.0
raw patch · 3 files changed
+163/−131 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.Record.Label: (<->) :: (a -> b) -> (b -> a) -> a :<->: b
- Data.Record.Label: Label :: Point f a a -> :-> f a
- Data.Record.Label: get :: (f :-> a) -> f -> a
- Data.Record.Label: iso :: (Iso f) => a :<->: b -> f a -> f b
- Data.Record.Label: label :: Getter f a -> Setter f a -> f :-> a
- Data.Record.Label: mod :: (f :-> a) -> (a -> a) -> f -> f
- Data.Record.Label: osi :: (Iso f) => a :<->: b -> f b -> f a
- Data.Record.Label: set :: (f :-> a) -> a -> f -> f
- Data.Record.Label: type Getter f o = f -> o
- Data.Record.Label: type Modifier f i o = (o -> i) -> f -> f
- Data.Record.Label: type Setter f i = i -> f -> f
+ Data.Record.Label: (%) :: (Iso f) => a :<->: b -> f a -> f b
+ Data.Record.Label: (:<->:) :: (a -> b) -> (b -> a) -> :<->: a b
+ Data.Record.Label: Point :: (f -> o) -> (i -> f -> f) -> Point f i o
+ Data.Record.Label: askM :: (MonadReader r m) => (r :-> b) -> m b
+ Data.Record.Label: getL :: (f :-> a) -> f -> a
+ Data.Record.Label: lens :: (f -> a) -> (a -> f -> f) -> f :-> a
+ Data.Record.Label: localM :: (MonadReader r m) => (r :-> b) -> (b -> b) -> m a -> m a
+ Data.Record.Label: mkLabelsNoTypes :: [Name] -> Q [Dec]
+ Data.Record.Label: modL :: (f :-> a) -> (a -> a) -> f -> f
+ Data.Record.Label: setL :: (f :-> a) -> a -> f -> f
- Data.Record.Label: Lens :: (a -> b) -> (b -> a) -> :<->: a b
+ Data.Record.Label: Lens :: Point f a a -> :-> f a
Files
- fclabels.cabal +52/−42
- src/Data/Record/Label.hs +61/−64
- src/Data/Record/Label/TH.hs +50/−25
fclabels.cabal view
@@ -1,21 +1,23 @@ Name: fclabels-Version: 0.4.2.1+Version: 0.9.0 Author: Sebastiaan Visser, Erik Hesselink, Chris Eidhof, Sjoerd Visscher.-Synopsis: First class accessor labels.+Synopsis: First class accessor labels implemented as lenses. Description: First class labels that act as bidirectional record fields.- The labels are fully composable and can be used to get, set- and modify parts of a datatype in a consistent way. The label- datatype, conveniently called `:->', is an instance of the- `Category' type class meaning it has a proper identity and- composition. The library has support for automatically- deriving labels from record selectors that start with an- underscore. Labels can be used in a purely functional setting- or be applied to mutable state in some state monad. .- To illustrate this package take the following two example- datatypes (somehow Haddock removes the braces):+ The labels are implemented as lenses and are fully composable+ and can be used to get, set and modify parts of a datatype in+ a consistent way. The lens datatype, conveniently called+ `:->', is an instance of the `Category' type class: meaning it+ has a proper identity and composition. The library has support+ for automatically deriving labels from record selectors that+ start with an underscore. Labels can be used in a purely+ functional setting or be applied to mutable state in some+ state monad. .+ To illustrate this package, let's take the following two example+ datatypes (somehow Haddock removes the curly braces):+ . > data Person = Person { > _name :: String > , _age :: Int@@ -29,18 +31,16 @@ > , _continent :: String > } .- Both are record datatypes with all record labels prefixed with+ Both are record datatypes with all record labels prefixed by an underscore. This underscore is an indication for our- Template Haskell code to derive labels for these fields.- Deriving labels can be done with this simple one-liner:+ Template Haskell code to derive lenses for these fields.+ Deriving lenses can be done with this simple one-liner: .- > $(mkLabels [''Person, ''Place])+ > $(mkLenses [''Person, ''Place]) .- Label function will be generated, label type signatures will- not. This is actually not that bad, by writing the signatures- down yourself you will be able to give them documentation,- something that would be hard otherwise. So, lets give- functions a signature by hand:+ Lens functions will be generated, lens type signatures will+ not be generated. This is actually not that bad, by writing the signatures+ Let's give the functions a signature by hand: . > name :: Person :-> String > age :: Person :-> Int@@ -52,24 +52,23 @@ . These type signatures look very similar to the function types for normal record labels, except that the additional colon- indicates a true first class label. These labels can be used+ indicates a true first class lens. These lenses can be used to get, set and modify the value and are fully composable. .- Now let look at this example. This 71 old fellow, called Jan,+ Now let's look at this example. This 71 year old fellow, called Jan, is my neighbour and didn't mind using him as an example: . > jan :: Person > jan = Person "Jan" 71 True (Place "Utrecht" "The Netherlands" "Europe") . When we want to be sure Jan is really as old as he claims we- can use the @get@ function for labels to get the age out as an- integer:+ can use the @get@ function to get the age out as an integer: . > hisAge :: Int > hisAge = get age jan .- Consider he now wants to move to Amsterdam, what better place- to spend your old days. Using composition when can change the+ Consider he now wants to move to Amsterdam: what better place+ to spend your old days. Using composition we can change the city value deep inside the structure: . > moveToAmsterdam :: Person -> Person@@ -83,8 +82,8 @@ and hide the default @(.)@, @id@ and @mod@ function from the Prelude. .- Now, because Jan is an old guy moving to another city is not a- very easy task, this really takes a while. So it will probably+ Now, because Jan is an old guy, moving to another city is not a+ very easy task, this really takes a while. It will probably take no less than two years before he will actually be settled. To reflect this change it might be useful to have a first class view on the @Person@ data type that only reveals@@ -96,15 +95,15 @@ . Because the applicative type class on its own is not very capable of expressing bidirectional relations, which we need- for our labels, the actual instance is defined for an internal+ for our lenses, the actual instance is defined for an internal helper structure called @Point@. Points are a bit more general- than labels. As you can see above the @Label@ constructor has+ than lenses. As you can see above, the @Label@ constructor has to be used to convert a @Point@ back into a @Label@. The @for@ function must be used to indicate which partial destructor to- use for which label in the applicative composition.+ use for which lens in the applicative composition. . Now that we have an appropriate age+city view on the @Person@- data type (which is itself a label again), we can use the+ data type (which is itself a lens again), we can use the @mod@ function to make Jan move to Amsterdam over exactly two years: .@@ -115,21 +114,30 @@ > Person "Jan" 73 True (Place "Amsterdam" "The Netherlands" "Europe") . This package also contains a lens data type that encodes- bidirectional functions. Just like labels lenses can be- composed to other lenses using the @Control.Category@ type- class. Lenses can be used to change the type of a label. The+ bidirectional functions. Just like lenses, lenses can be+ composed with other lenses using the @Control.Category@ type+ class. Lenses can be used to change the type of a lens. The @Iso@ type class, which can be seen as a bidirectional- functor, can be used to apply lenses to labels. For example,- when we want to threat the age of a person as a string we can+ functor, can be used to apply lenses to lenses. For example,+ when we want to treat the age of a person as a string we can do the following: . > ageAsString :: Person :-> String > ageAsString :: (show <-> read) `iso` age .- This library might look cryptic at first sight, but give it a- try it is not that hard.+ A final note: this library might look cryptic at first sight, but give it a+ try, it is not that hard.+ .+ .+ > CHANGELOG+ > 0.4.2 -> 1.0.0+ > - Added askM and localM for running lenses inside MonadReader.+ > - Minor documentaion update.+ > - Exported Point internals.+ > - Renamed Lens to Bijection, which is more correct.+ > - Renamed Label to Lens. -Maintainer: Sebastiaan Visser <sfvisser@cs.uu.nl>+Maintainer: Sebastiaan Visser <haskell@fvisser.nl> License: BSD3 License-File: LICENCE Category: Data@@ -138,5 +146,7 @@ HS-Source-Dirs: src Exposed-Modules: Data.Record.Label Other-Modules: Data.Record.Label.TH-Build-Depends: base >= 3 && < 5, template-haskell >= 2.2 && < 2.5, monads-fd >=0.0 && < 0.2+Build-Depends: base >= 3 && < 5+ , template-haskell >= 2.2 && < 2.5+ , monads-fd >= 0.0 && < 0.2
src/Data/Record/Label.hs view
@@ -1,78 +1,69 @@ {-# LANGUAGE TypeOperators, TypeSynonymInstances, TemplateHaskell #-} module Data.Record.Label (- -- * Getter, setter and modifier types.- Getter- , Setter- , Modifier-- -- * Label type.- , Point- , (:->) (Label)- , label- , get, set, mod+ -- * Lens types.+ Point (Point)+ , (:->) (Lens)+ , lens+ , getL, setL, modL , fmapL -- * Bidirectional functor. , (:<->:) (..)- , (<->) , Iso (..) , lmap , for - -- * State monadic label operations.-+ -- * Monadic lens operations. , getM, setM, modM, (=:)+ , askM, localM -- * Derive labels using Template Haskell. , module Data.Record.Label.TH ) where -import Prelude hiding ((.), id, mod)+import Prelude hiding ((.), id) import Control.Applicative import Control.Category-import Control.Monad.State hiding (get)+import Control.Monad.State+import Control.Monad.Reader import Data.Record.Label.TH -type Getter f o = f -> o-type Setter f i = i -> f -> f-type Modifier f i o = (o -> i) -> f -> f- data Point f i o = Point- { _get :: Getter f o- , _set :: Setter f i+ { _get :: f -> o+ , _set :: i -> f -> f } _mod :: Point f i o -> (o -> i) -> f -> f _mod l f a = _set l (f (_get l a)) a -newtype (f :-> a) = Label { unLabel :: Point f a a }+newtype (f :-> a) = Lens { unLens :: Point f a a } --- Create a label out of a getter and setter.+-- | Create a lens out of a getter and setter. -label :: Getter f a -> Setter f a -> f :-> a-label g s = Label (Point g s)+lens :: (f -> a) -> (a -> f -> f) -> f :-> a+lens g s = Lens (Point g s) --- | Get the getter function from a label.+-- | Get the getter function from a lens. -get :: (f :-> a) -> f -> a-get = _get . unLabel+getL :: (f :-> a) -> f -> a+getL = _get . unLens --- | Get the setter function from a label.+-- | Get the setter function from a lens. -set :: (f :-> a) -> a -> f -> f-set = _set . unLabel+setL :: (f :-> a) -> a -> f -> f+setL = _set . unLens --- | Get the modifier function from a label.+-- | Get the modifier function from a lens. -mod :: (f :-> a) -> (a -> a) -> f -> f-mod = _mod . unLabel+modL :: (f :-> a) -> (a -> a) -> f -> f+modL = _mod . unLens instance Category (:->) where- id = Label (Point id const)- (Label a) . (Label b) = Label (Point (_get a . _get b) (_mod b . _set a))+ id = lens id const+ Lens a . Lens b = lens (_get a . _get b) (_mod b . _set a) instance Functor (Point f i) where fmap f x = Point (f . _get x) (_set x)@@ -82,61 +73,56 @@ a <*> b = Point (_get a <*> _get b) (\r -> _set b r . _set a r) fmapL :: Applicative f => (a :-> b) -> f a :-> f b-fmapL l = label (fmap (get l)) (\x f -> set l <$> x <*> f)+fmapL l = lens (fmap (getL l)) (\x f -> setL l <$> x <*> f) -- | This isomorphism type class is like a `Functor' but works in two directions. class Iso f where- iso :: a :<->: b -> f a -> f b- iso (Lens a b) = osi (b <-> a)- osi :: a :<->: b -> f b -> f a- osi (Lens a b) = iso (b <-> a)---- | The lens datatype, a function that works in two directions. To bad there--- is no convenient way to do application for this.+ (%) :: a :<->: b -> f a -> f b -data a :<->: b = Lens { fw :: a -> b, bw :: b -> a }+-- | The bijections datatype, a function that works in two directions. --- | Constructor for lenses.+infixr 7 :<->:+data a :<->: b = (:<->:) { fw :: a -> b, bw :: b -> a } -infixr 7 <->-(<->) :: (a -> b) -> (b -> a) -> a :<->: b-a <-> b = Lens a b+-- | Constructor for bijections. instance Category (:<->:) where- id = Lens id id- (Lens a b) . (Lens c d) = Lens (a . c) (d . b)+ id = id :<->: id+ (a :<->: b) . (c :<->: d) = a . c :<->: d . b +infixr 8 %+ instance Iso ((:->) i) where- iso l (Label a) = Label (Point (fw l . _get a) (_set a . bw l))+ l % Lens a = lens (fw l . _get a) (_set a . bw l) instance Iso ((:<->:) i) where- iso = (.)+ (%) = (.) lmap :: Functor f => (a :<->: b) -> f a :<->: f b -lmap l = let (Lens a b) = l in fmap a <-> fmap b+lmap l = let a :<->: b = l in fmap a :<->: fmap b dimap :: (o' -> o) -> (i -> i') -> Point f i' o' -> Point f i o dimap f g l = Point (f . _get l) (_set l . g) --- | Combine a partial destructor with a label into something easily used in--- the applicative instance for the hidden `Point' datatype. Internally uses--- the covariant in getter, contravariant in setter bi-functioral-map function.+-- | Combine a partial destructor with a lens into something easily used in the+-- applicative instance for the hidden `Point' datatype. Internally uses the+-- covariant in getter, contravariant in setter bi-functioral-map function. -- (Please refer to the example because this function is just not explainable -- on its own.) for :: (i -> o) -> (f :-> o) -> Point f i o-for a b = dimap id a (unLabel b)+for a b = dimap id a (unLens b) --- | Get a value out of state pointed to by the specified label.+-- | Get a value out of state pointed to by the specified lens. getM :: MonadState s m => s :-> b -> m b-getM = gets . get+getM = gets . getL --- | Set a value somewhere in state pointed to by the specified label.+-- | Set a value somewhere in state pointed to by the specified lens. setM :: MonadState s m => s :-> b -> b -> m ()-setM l = modify . set l+setM l = modify . setL l -- | Alias for `setM' that reads like an assignment. @@ -145,8 +131,19 @@ (=:) = setM -- | Modify a value with a function somewhere in state pointed to by the--- specified label.+-- specified lens. modM :: MonadState s m => s :-> b -> (b -> b) -> m ()-modM l = modify . mod l+modM l = modify . modL l++-- | Fetch a value pointed to by a lens out of a reader environment.++askM :: MonadReader r m => (r :-> b) -> m b+askM = asks . getL++-- | Execute a computation in a modified environment. The lens is used to+-- point out the part to modify.++localM :: MonadReader r m => (r :-> b) -> (b -> b) -> m a -> m a+localM l f = local (modL l f)
src/Data/Record/Label/TH.hs view
@@ -1,38 +1,63 @@-module Data.Record.Label.TH (mkLabels) where+module Data.Record.Label.TH (mkLabels, mkLabelsNoTypes) where import Control.Monad import Data.Char import Language.Haskell.TH.Syntax --- | Derive labels for all the record selector in a datatype.+-- | Derive lenses including type signatures for all the record selectors in a datatype. mkLabels :: [Name] -> Q [Dec]-mkLabels = liftM concat . mapM mkLabels1+mkLabels = liftM concat . mapM (mkLabels1 True) -mkLabels1 :: Name -> Q [Dec]-mkLabels1 n = do+-- | Derive lenses without type signatures for all the record selectors in a datatype.+mkLabelsNoTypes :: [Name] -> Q [Dec]+mkLabelsNoTypes = liftM concat . mapM (mkLabels1 False)++-- Helpers.++mkLabels1 :: Bool -> Name -> Q [Dec]+mkLabels1 sigs n = do i <- reify n- let -- only process data and newtype declarations- cs' = case i of- TyConI (DataD _ _ _ cs _) -> cs- TyConI (NewtypeD _ _ _ c _) -> [c]- _ -> []- -- we're only interested in labels of record constructors+ let -- only process data and newtype declarations, filter out all constructors and the type variables+ (cs',vars) = case i of+ TyConI (DataD _ _ vs cs _) -> (cs , vs)+ TyConI (NewtypeD _ _ vs c _) -> ([c], vs)+ _ -> ([], undefined)+ -- we are only interested in lenses of record constructors ls' = [ l | RecC _ ls <- cs', l <- ls ]- return (map mkLabel1 ls')+ return (concatMap (mkLabel1 sigs n vars) ls') -mkLabel1 :: VarStrictType -> Dec-mkLabel1 (name, _, _) =- -- Generate a name for the label:- -- If the original selector starts with an _, remove it and make the next- -- character lowercase. Otherwise, add 'l', and make the next character- -- uppercase.- let n = mkName $ case nameBase name of+mkLabel1 :: Bool -> Name -> [TyVarBndr] -> VarStrictType -> [Dec]+mkLabel1 sigs typeName binders (name, _, t) =+ let -- Generate a name for the lens:+ -- If the original selector starts with an _, remove it and make the next+ -- character lowercase. Otherwise, add 'l', and make the next character+ -- uppercase.+ lensName = mkName $ case nameBase name of ('_' : c : rest) -> toLower c : rest (f : rest) -> 'l' : toUpper f : rest- _ -> ""- in FunD n [Clause [] (NormalB (- AppE (AppE (VarE (mkName "label")) (VarE name)) -- getter- (LamE [VarP (mkName "b"), VarP (mkName "a")] -- setter- (RecUpdE (VarE (mkName "a")) [(name, VarE (mkName "b"))]))- )) []]+ _ -> error "Invalid name" + -- The source type of a lens+ source = foldl appTv (ConT typeName) binders++ -- The type of the lens+ lensType = ForallT binders [] $ AppT (AppT (ConT $ mkName ":->") source) t++ in (if sigs then [SigD lensName lensType] else []) ++ [functionBody lensName name]++appTv :: Type -> TyVarBndr -> Type+appTv t (PlainTV n) = AppT t (VarT n)+appTv _ v = error $ "Kinded type variable not supported: " ++ show v+++functionBody :: Name -> Name -> Dec+functionBody lensName fieldName = + FunD lensName [+ Clause [] (+ NormalB (+ AppE (AppE (VarE (mkName "lens")) (VarE fieldName)) -- getter+ (LamE [VarP (mkName "b"), VarP (mkName "a")] -- setter+ (RecUpdE (VarE (mkName "a")) [(fieldName, VarE (mkName "b"))])+ )+ )+ ) [] ]