packages feed

fclabels 0.3.0 → 0.4.0

raw patch · 3 files changed

+212/−89 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Record.Label: (%%) :: (Functor f) => a :-> b -> g :-> f a -> g :-> f b
- Data.Record.Label: (%) :: (g :-> a) -> (f :-> g) -> (f :-> a)
- Data.Record.Label: class Lens f
- Data.Record.Label: idL :: a :-> a
- Data.Record.Label: instance Lens ((:->) f)
- Data.Record.Label: lget :: :-> a b -> Getter a b
- Data.Record.Label: list :: Int -> [a] :-> a
- Data.Record.Label: lmap :: (Lens f) => (a -> b, b -> a) -> f a -> f b
- Data.Record.Label: lmod :: :-> a b -> Modifier a b
- Data.Record.Label: lset :: :-> a b -> Setter a b
- Data.Record.Label: maybeNull :: [a] :-> Maybe [a]
- Data.Record.Label: mkLabel :: Getter a b -> Setter a b -> a :-> b
- Data.Record.Label: mkModifier :: Getter a b -> Setter a b -> Modifier a b
+ Data.Record.Label: (<->) :: (a -> b) -> (b -> a) -> a :<->: b
+ Data.Record.Label: Lens :: (a -> b) -> (b -> a) -> :<->: a b
+ Data.Record.Label: bw :: :<->: a b -> b -> a
+ Data.Record.Label: class Iso f
+ Data.Record.Label: data Point f i o
+ Data.Record.Label: for :: (i -> o) -> (f :-> o) -> Point f i o
+ Data.Record.Label: fw :: :<->: a b -> a -> b
+ Data.Record.Label: get :: (f :-> a) -> f -> a
+ Data.Record.Label: instance Applicative (Point f i)
+ Data.Record.Label: instance Category :<->:
+ Data.Record.Label: instance Functor (Point f i)
+ Data.Record.Label: instance Iso ((:->) i)
+ 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: newtype (:->) f a
+ Data.Record.Label: osi :: (Iso f) => a :<->: b -> f b -> f a
+ Data.Record.Label: set :: (f :-> a) -> a -> f -> f
- Data.Record.Label: Label :: Getter a b -> Setter a b -> Modifier a b -> :-> a b
+ Data.Record.Label: Label :: Point f a a -> :-> f a
- Data.Record.Label: data (:->) a b
+ Data.Record.Label: data (:<->:) a b
- Data.Record.Label: type Getter a b = a -> b
+ Data.Record.Label: type Getter f o = f -> o
- Data.Record.Label: type Modifier a b = (b -> b) -> a -> a
+ Data.Record.Label: type Modifier f i o = (o -> i) -> f -> f
- Data.Record.Label: type Setter a b = b -> a -> a
+ Data.Record.Label: type Setter f i = i -> f -> f

Files

Data/Record/Label.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeOperators, TypeSynonymInstances, TemplateHaskell #-} module Data.Record.Label   (   -- * Getter, setter and modifier types.@@ -7,93 +7,124 @@   , Modifier    -- * Label type.-  , (:->) (..)-  , mkModifier-  , mkLabel--  -- * Identity and composition.-  , idL-  , (%)+  , Point+  , (:->) (Label)+  , label+  , get, set, mod    -- * Bidirectional functor.--  , Lens (..)-  , (%%)+  , (:<->:) (..)+  , (<->)+  , Iso (..)+  , for    -- * State monadic label operations.    , getM, setM, modM, (=:) -  -- * Useful example labels.-  , list-  , maybeNull-   -- * Derive labels using Template Haskell.   , module Data.Record.Label.TH--  ) where+  )+where -import Prelude hiding ((.), id)+import Prelude hiding ((.), id, mod)+import Control.Applicative import Control.Category-import Control.Monad.State+import Control.Monad.State hiding (get) import Data.Record.Label.TH -type Getter   a b = a -> b-type Setter   a b = b -> a -> a-type Modifier a b = (b -> b) -> a -> a+type Getter   f o   = f -> o+type Setter   f i   = i -> f -> f+type Modifier f i o = (o -> i) -> f -> f -data a :-> b = Label-  { lget :: Getter   a b-  , lset :: Setter   a b-  , lmod :: Modifier a b+data Point f i o = Point+  { _get :: Getter f o+  , _set :: Setter f i   } --- | Create a modifier function out of a getter and a setter.+_mod :: Point f i o -> (o -> i) -> f -> f+_mod l f a = _set l (f (_get l a)) a -mkModifier :: Getter a b -> Setter a b -> Modifier a b-mkModifier gg ss f a = ss (f (gg a)) a+newtype (f :-> a) = Label { unLabel :: Point f a a } --- | Smart constructor for `Label's, the modifier will be computed based on--- getter and setter.+-- Create a label out of a getter and setter. -mkLabel :: Getter a b -> Setter a b -> a :-> b-mkLabel g s = Label g s (mkModifier g s)+label :: Getter f a -> Setter f a -> f :-> a+label g s = Label (Point g s) -idL :: a :-> a-idL = mkLabel id const+-- | Get the getter function from a label. -infixr 8 %-(%) :: (g :-> a) -> (f :-> g) -> (f :-> a)-a % b = Label (lget a . lget b) (lmod b . lset a) (lmod b . lmod a)+get :: (f :-> a) -> f -> a+get = _get . unLabel +-- | Get the setter function from a label.++set :: (f :-> a) -> a -> f -> f+set = _set . unLabel++-- | Get the modifier function from a label.++mod :: (f :-> a) -> (a -> a) -> f -> f+mod = _mod . unLabel+ instance Category (:->) where-  id = idL-  (.) = (%)+  id = Label (Point id const)+  (Label a) . (Label b) = Label (Point (_get a . _get b) (_mod b . _set a)) --- Apply custom `parser' and 'printer' function. This can be seen as a--- bidirectional functorial map.+instance Functor (Point f i) where+  fmap f x = Point (f . _get x) (_set x) -class Lens f where-  lmap :: (a -> b, b -> a) -> f a -> f b+instance Applicative (Point f i) where+  pure a = Point (const a) (const id)+  a <*> b = Point (\f -> _get a f (_get b f)) (\r -> _set b r . _set a r) -instance Lens ((:->) f) where-  lmap (f, g) (Label a b c) = Label (f . a) (b . g) (c . (g.) . (.f))+-- | This isomorphism type class is like a `Functor' but works in two directions. --- | Apply label to lifted value and join afterwards.+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) -infixr 8 %%-(%%) :: Functor f => a :-> b -> g :-> f a -> g :-> f b-(%%) a b = let (Label g s _) = a in (fmap g, fmap (\k -> s k (error "unused"))) `lmap` b+-- | The lens datatype, a function that works in two directions. To bad there+-- is no convenient way to do application for this. +data a :<->: b = Lens { fw :: a -> b, bw :: b -> a }++-- | Constructor for lenses.++infixr 7 <->+(<->) :: (a -> b) -> (b -> a) -> a :<->: b+a <-> b = Lens a b++instance Category (:<->:) where+  id = Lens id id+  (Lens a b) . (Lens c d) = Lens (a . c) (d . b)++instance Iso ((:->) i) where+  iso l (Label a) = Label (Point (fw l . _get a) (_set a . bw l))++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.+-- (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)+ -- | Get a value out of state pointed to by the specified label.  getM :: MonadState s m => s :-> b -> m b-getM = gets . lget+getM = gets . get  -- | Set a value somewhere in state pointed to by the specified label.  setM :: MonadState s m => s :-> b -> b -> m ()-setM l = modify . lset l+setM l = modify . set l  -- | Alias for `setM' that reads like an assignment. @@ -105,15 +136,5 @@ -- specified label.  modM :: MonadState s m => s :-> b -> (b -> b) -> m ()-modM l = modify . lmod l---- Lift list indexing to a label.--list :: Int -> [a] :-> a-list i = mkLabel (!! i) (\v a -> take i a ++ [v] ++ drop (i+1) a)---- View null lists as Nothing.--maybeNull :: [a] :-> Maybe [a]-maybeNull = (\x -> if null x then Nothing else Just x, maybe [] id) `lmap` id+modM l = modify . mod l 
Data/Record/Label/TH.hs view
@@ -1,22 +1,10 @@ module Data.Record.Label.TH (mkLabels) where -import Control.Monad (liftM)-import Data.Char (toLower, toUpper)-import Language.Haskell.TH-  ( Body (NormalB)-  , Clause (Clause)-  , Con (RecC)-  , Dec (DataD, FunD)-  , Exp (AppE, LamE, RecUpdE, VarE)-  , Info (TyConI)-  , Name-  , Pat (VarP)-  , Q-  , mkName-  , nameBase-  , reify)-import Language.Haskell.TH.Syntax (VarStrictType)+import Control.Monad+import Data.Char+import Language.Haskell.TH.Syntax +-- | Derive labels for all the record selector in a datatype. mkLabels :: [Name] -> Q [Dec] mkLabels = liftM concat . mapM mkLabels1 @@ -40,13 +28,8 @@                 (f : rest)       -> 'l' : toUpper f : rest                 _                -> ""     in FunD n [Clause [] (NormalB (-           AppE (AppE (VarE (mkName "mkLabel"))-                      (VarE name)) -- getter+           AppE (AppE (VarE (mkName "label")) (VarE name)) -- getter                 (LamE [VarP (mkName "b"), VarP (mkName "a")] -- setter                       (RecUpdE (VarE (mkName "a")) [(name, VarE (mkName "b"))]))                                    )) []]--{-isRec :: Con -> Bool-isRec (RecC _ _) = True-isRec _          = False-} 
fclabels.cabal view
@@ -1,15 +1,134 @@ Name:            fclabels-Version:         0.3.0-Author:          Sebastiaan Visser, Erik Hesselink+Version:         0.4.0+Author:          Sebastiaan Visser, Erik Hesselink, Chris Eidhof, Sjoerd Visscher. Synopsis:        First class accessor labels.+ Description:     First class labels that act as bidirectional records fields.                  The labels are fully composable and can be used to get, set                  and modify part of datatypes in a consistent way. The label                  datatype, conveniently called `:->', is an instance of the-                 `Category' type class, so is has a proper identity and+                 `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.+                 underscore. Labels can be used in a pure 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):+                 .+                 > data Person = Person {+                 >     _name   :: String+                 >   , _age    :: Int+                 >   , _isMale :: Bool+                 >   , _place  :: Place+                 >   }+                 .+                 > data Place = Place {+                 >     _city+                 >   , _country+                 >   , _continent :: String+                 >   }+                 .+                 Both are record datatypes with all record labels prefixed with+                 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:+                 .+                 > $(mkLabels [''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:+                 .+                 > name      :: Person :-> String+                 > age       :: Person :-> Int+                 > isMale    :: Person :-> Bool+                 > place     :: Person :-> Place+                 > city      :: Place :-> String+                 > country   :: Place :-> String+                 > continent :: Place :-> String+                 .+                 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+                 to get, set and modify the value and are fully composable.+                 .+                 Now let look at this example. This 71 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:+                 .+                 > 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+                 city value deep inside the structure:+                 .+                 > moveToAmsterdam :: Person -> Person+                 > moveToAmsterdam = set (city . place) "Amsterdam"+                 .+                 > moveToAmsterdam jan ==+                 >  Person "Jan" 71 True (Place "Amsterdam" "The Netherlands" "Europe")+                 .+                 Composition is done using the dot operator which is part of+                 the @Control.Category@ module. Make sure to import this module+                 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+                 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+                 the age and city.  This can be done by using a neat+                 @Applicative@ functor instance:+                 .+                 > ageAndCity :: Person :-> (Int, String)+                 > ageAndCity = Label $ (,) <$> fst `for` age <*> snd `for` (city . place)+                 .+                 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+                 helper structure called @Point@. Points are a bit more general+                 than labels. 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.+                 .+                 Now that we have an appropriate age+city view on the @Person@+                 data type (which is itself a label again), we can use the+                 @mod@ function to make Jan move to Amsterdam over exactly two+                 years:+                 .+                 > moveToAmsterdamOverTwoYears :: Person -> Person+                 > moveToAmsterdamOverTwoYears = mod ageAndCity (\(a, b) -> (a+2, "Amsterdam"))+                 .+                 > moveToAmsterdamOverTwoYears jan ==+                 >  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+                 @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+                 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.+ Maintainer:      Sebastiaan Visser <sfvisser@cs.uu.nl> License:         BSD3 License-File:    LICENCE