packages feed

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 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"))])+               )+                 )+              ) []    ]