-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | First-class record field combinators with infix record field syntax. -- -- Using records, especially nested records, in Haskell can sometimes be -- a bit of a chore. Fortunately, there are several libraries in hackage -- that make working with records easier. This library is my attempt to -- build on top of these libraries to make working with records even more -- pleasant! -- -- In most imperative languages, records are accessed using the infix dot -- operator. Record fields can be read simply by suffixing a record value -- with '.field' and they can be modified by simply assigning to that -- location. Although this is not the only way to access records (indeed, -- Haskell does not use it), many people (including myself) like it. This -- library attempts to support this style for Haskell records in the -- following manner: -- --
-- record.field.subfield becomes record .# field # subfield -- record.field = value becomes record .# field =: value ---- -- Of course, the infix assignment in Haskell is pure and doesn't -- actually mutate anything. Rather, a modified version of the record is -- returned. -- -- In addition, the following features are supported: -- --
-- r.#f =: v ---- -- returns a modified version of r so that the field -- corresponding to f are set to v. (=:) :: (Field a) => a -> Dst a -> Src a :-> Src a -- | Infix modification lookalike. -- --
-- r.#f =~ g ---- -- returns a modified version of r so that the fields -- corresponding to f are modified with the function g. (=~) :: (Field a) => a -> (Dst a -> Dst a) -> Src a :-> Src a -- | Convenience function for use with the ViewPatterns extension. -- --
-- case r of -- (match int -> 5) -> "It's 5!" -- (match (int,str#$length) -> (i,l)) -- | i == l -> "They're equal!" -- | otherwise -> "Not equal." -- _ -> "Something else." --match :: (Field a) => a -> Src a -> Dst a instance Field (a :-> b) -- | Composition operators for collection fields. module Data.Record.Field.Indexable -- | Class of collection types that can be indexed into. -- -- TODO: This should probably be a single-parameter type class with two -- associated types instead. class Indexable a i where { type family Element a :: *; { unsafeIndexGet i a = maybe notFound id $ indexGet i a where notFound = error "unsafeIndexGet: element not found" } } indexGet :: (Indexable a i) => i -> a -> Maybe (Element a) indexSet :: (Indexable a i) => i -> Maybe (Element a) -> a -> a unsafeIndexGet :: (Indexable a i) => i -> a -> Element a -- | Compose a field with an Indexable collection safely. -- --
-- r .# coll #! idx ---- -- returns Nothing if idx was not found from the -- collection, and Just v if v was found. -- --
-- r .# coll #! idx =: Just v ---- -- sets the value at idx in the collection to be v. If -- the value wasn't in the collection, it's inserted. The exact semantics -- of insertion depend on the actual collection in question. -- --
-- r .# coll #! idx =: Nothing ---- -- removes the value at idx from the collection, if possible. (#!) :: (Field a, Indexable (Dst a) i) => a -> i -> Src a :-> Maybe (Element (Dst a)) -- | As (#!), but reading a nonexistent value will likely result -- in a bottom value being returned. Also, the resulting field cannot be -- used to remove values. (#!!) :: (Field a, Indexable (Dst a) i) => a -> i -> Src a :-> Element (Dst a) instance Indexable IntSet Int instance (a1 ~ a2, Ord a1) => Indexable (Set a1) a2 instance (i1 ~ i2, IArray a e, Ix i1) => Indexable (a i1 e) i2 instance Indexable (IntMap a) Int instance (k1 ~ k2, Ord k1) => Indexable (Map k1 a) k2 instance (Integral i) => Indexable [a] i -- | Field combinators. module Data.Record.Field.Combinators -- | Identity lens. idL :: a :-> a -- | Field composition with arguments in OO-like order. (#) :: (Field a, Field b, (Dst a) ~ (Src b)) => a -> b -> Src a :-> Dst b -- | Compose fields with ordinary functions. As functions are one-way, the -- resulting field cannot be used to set values. (#$) :: (Field a) => a -> (Dst a -> b) -> Src a :-> b -- | Infix fmap for fields. -- -- Examples: -- --
-- persons <.#> firstName ---- --
-- do (v1, v2) <- takeMVar mv <.#> (field1, field2) -- putStrLn . unlines $ [ "v1: " ++ show v1, "v2: " ++ show v2 ] --(<.#>) :: (Functor f, Field a) => f (Src a) -> a -> f (Dst a) -- | Applicative functor composition for fields. -- --
-- book .# characters <#> lastName --(<#>) :: (Applicative f, Field a, Field b, (Dst a) ~ (f (Src b))) => a -> b -> Src a :-> f (Dst b) -- | Flattening monadic composition for fields. -- --
-- person .# superior <##> superior <##> superior <##> superior --(<##>) :: (Monad m, Field a, Field b, (Dst a) ~ (m (Src b)), (Dst b) ~ (m c)) => a -> b -> Src a :-> m c -- | Zippy field reference to be used with (=*). -- --
-- [ rec1, rec2 ] *# field =* [ value1, value2 ] --(*#) :: (Field b) => [Src b] -> [b] -> [Dst b] -- | Zippy infix assignment to be used with (*#). (=*) :: (Field a) => a -> [Dst a] -> [Src a :-> Src a] -- | Infix assignment for the State monad. -- --
-- (field1, field2) <=: (value1, value2) --(<=:) :: (MonadState (Src a) m, Field a) => a -> Dst a -> m () -- | Infix modification for the State monad. -- --
-- (field1, field2) <=~ (f, g) --(<=~) :: (MonadState (Src a) m, Field a) => a -> (Dst a -> Dst a) -> m () -- | Utility combinator in the manner of -- Data.Function.on. -- --
-- sortBy (compare `onField` (lastName,firstName)) persons --onField :: (Field a) => (Dst a -> Dst a -> t) -> a -> Src a -> Src a -> t -- | Instances for tuples of fields up to a 10-tuple. This allows accessing -- several fields simultaneously. -- --
-- r.#(field1, field2, field3#field4) =: (value1, value2, value3) ---- -- In addition, the pair instance is recursively defined, which allows -- stuff like -- --
-- import Control.Arrow ((***)) -- r.#(field1, (field2, field3)) =~ (f *** g *** h) --module Data.Record.Field.Tuple instance Field (r :-> a, r :-> b, r :-> c, r :-> d, r :-> e, r :-> f, r :-> g, r :-> h, r :-> i, r :-> j) instance Field (r :-> a, r :-> b, r :-> c, r :-> d, r :-> e, r :-> f, r :-> g, r :-> h, r :-> i) instance Field (r :-> a, r :-> b, r :-> c, r :-> d, r :-> e, r :-> f, r :-> g, r :-> h) instance Field (r :-> a, r :-> b, r :-> c, r :-> d, r :-> e, r :-> f, r :-> g) instance Field (r :-> a, r :-> b, r :-> c, r :-> d, r :-> e, r :-> f) instance Field (r :-> a, r :-> b, r :-> c, r :-> d, r :-> e) instance Field (r :-> a, r :-> b, r :-> c, r :-> d) instance Field (r :-> a, r :-> b, r :-> c) instance (r ~ Src f, Field f) => Field (r :-> a, f) -- | Using records, especially nested records, in Haskell can sometimes be -- a bit of a chore. Fortunately, there are several libraries in hackage -- to make working with records easier. This library is my attempt to -- build on top of these libraries to make working with records even more -- pleasant! -- -- In most imperative languages, records are accessed using the infix dot -- operator. Record fields can be read simply by suffixing a record value -- with '.field' and they can be modified by simply assigning to that -- location. Although this is not the only way to access records (indeed, -- Haskell does not use it), many people (including myself) like it. This -- library attempts to support this style for Haskell records in the -- following manner: -- --
-- record.field.subfield becomes record .# field # subfield -- record.field = value becomes record .# field =: value ---- -- Of course, the infix assignment in Haskell is pure and doesn't -- actually mutate anything. Rather, a modified version of the record is -- returned. -- -- Below, a detailed and commented usage example is presented. -- --
-- import Data.Record.Field -- import Data.Record.Label hiding ((=:)) ---- -- Currently, fields is built on top of -- fclabels, so we import that package as well. We hide -- the (=:) operator because that operator is also used by -- fields itself. -- -- First, let's define some example data types and derive lenses for them -- using fclabels. -- --
-- data Person = Person
-- { _firstName :: String
-- , _lastName :: String
-- , _age :: Int
-- , _superior :: Maybe Person
-- } deriving Show
--
-- data Book = Book
-- { _title :: String
-- , _author :: Person
-- , _characters :: [Person]
-- } deriving Show
--
-- $(mkLabels [''Person, ''Book])
--
--
-- Now, let's define some example data.
--
-- -- howard = Person "Howard" "Lovecraft" 46 Nothing -- charles = Person "Charles" "Ward" 26 Nothing -- marinus = Person "Marinus" "Willett" 56 Nothing -- william = Person "William" "Dyer" 53 Nothing -- frank = Person "Frank" "Pabodie" 49 Nothing -- herbert = Person "Herbert" "West" 32 Nothing -- abdul = Person "Abdul" "Alhazred" 71 Nothing -- -- mountains = Book "At the Mountains of Madness" undefined [] -- caseOfCDW = Book "The Case of Charles Dexter Ward" undefined [] -- reanimator = Book "Herbert West -- The Re-animator" undefined [] -- necronomicon = Book "Necronomicon" undefined [] -- -- persons = [howard, charles, marinus, herbert, william, frank, abdul] -- books = [mountains, caseOfCDW, reanimator, necronomicon] ---- -- Now, to look up a book's title, we can use the (.#) -- operator, which is the basis of all fields -- functionality. (.#) takes a value of type a -- and a Field from a to some other type (in -- this case, String) and returns the value of that field. Since -- an fclabels lens is an instance of -- Field, we can just use the lens directly. -- --
-- necronomicon .# title -- -- :: String ---- -- The author field, however, was left undefined in the above -- definition. We can set it using the (=:) operator -- --
-- necronomicon .# author =: abdul -- -- :: Book ---- -- A notable detail is that the above expression parenthesizes as -- necronomicon .# (author =: abdul). The (=:) operator -- takes a Field and a value for that -- Field and returns a new Field that, -- when read, returns a modified version of the record. -- -- For the sake of the example, I will assume here that the subsequent -- references to necronomicon refer to this modified version -- (and similarly for all other assignment examples below), even though -- nothing is mutated in reality. -- -- The (=~) operator is similar, except that instead of a -- value, it takes a function that modifies the previous value. For -- example -- --
-- howard .# age =~ succ -- -- :: Person ---- -- To access fields in nested records, Fields can be -- composed using the (#) combinator. -- --
-- necronomicon .# author # lastName -- -- :: String ---- -- If we wish to access a field of several records at once, we can use -- the (<.#>) operator, which can be used to access -- fields of a record inside a Functor. For example -- --
-- persons <.#> age -- -- :: [Int] ---- -- This also works for assignment. For example, let's fix the -- author fields of the rest of our books. -- --
-- [mountains, caseOfCDW, reanimator ] <.#> author =: howard -- -- :: [Book] ---- -- Because (<.#>) works for any -- Functor, we could access values of type Maybe -- Book, a -> Book or IO Book similarly. -- -- We frequently wish to access several fields of a record -- simultaneously. fields supports this using tuples. A -- tuple of primitive Fields (currently, "primitive -- Field" means an fclabels lens) is -- itself a Field, provided that all the -- Fields in the tuple have the same source type (ie. you -- can combine Book :-> String and Book :-> Int -- but not Book :-> String and Person :-> -- String). For example, we could do -- --
-- howard .# (firstName, lastName, age) -- -- :: (String, String, Int) ---- -- fields defines instances for tuples of up to 10 -- elements. In addition, the 2-tuple instance is recursively defined so -- that a tuple (a, b) is a Field if a -- is a primitive Field and b is any -- valid field. This makes it possible to do -- --
-- howard .# (firstName, (lastName, age)) =~ (reverse *** reverse *** negate) -- -- :: Person ---- -- We can also compose a Field with a pure function (for -- example, a regular record field accessor function) using the -- ('#$') combinator. However, since a function is one-way, the -- resulting Field cannot be used to set values, and -- trying to do so will result in an error. -- --
-- howard .# lastName #$ length -- -- :: Int ---- -- If we wish to set fields of several records at once, but so that we -- can also specify the value individually for each record, we can use -- the (*#) and (=*) operators, which can -- be thought of as "zippy" assignment. They can be used like this -- --
-- [ mountains, caseOfCDW, reanimator ] *# characters =* -- [ [ william, frank ] -- , [ charles, marinus ] -- , [ herbert ] ] -- -- :: [Book] ---- -- For more complex queries, fields also provides the -- (<#>) and (<##>) -- combinators. (<#>) combines a -- Field of type a :-> f b with a field of -- type b :-> c, producing a Field of type -- a :-> f c, where f is any -- Applicative functor. -- --
-- mountains .# characters <#> (lastName, age) -- -- :: [(String, Int)] ---- -- (<##>) is similar, except that flattens two -- monadic Fields together. I.e. the type signature is -- a :-> m b -> b :-> m c -> a :-> m c. For -- example -- --
-- frank .# superior <##> superior <##> superior -- -- :: Maybe Person ---- -- Both (<#>) and (<##>) also -- support assignment normally, although the exact semantics vary -- depending on the Applicative or -- Monad in question. -- -- We might also like to sort or otherwise manipulate collections of -- records easily. For this, fields provides the -- onField combinator in the manner of -- Data.Function.on. For example, to sort a list of -- books by their authors' last names, we can use -- --
-- sortBy (compare `onField` author # lastName) books -- -- :: [Book] ---- -- Using tuples, we can also easily define sub-orderings. For example, if -- we wish to break ties based on the authors' first names and then by -- ages, we can use -- --
-- sortBy (compare `onField` author # (lastName, firstName, age)) books -- -- :: [Book] ---- -- Since onField accepts any Field, we -- can easily specify more complex criteria. To sort a list of books by -- the sum of their characters' ages (which is a bit silly), we could use -- --
-- sortBy (compare `onField` (characters <#> age) #$ sum) books -- -- :: [Book] ---- -- fields also attempts to support convenient pattern -- matching by means of the match function and GHC's -- ViewPatterns extension. To pattern match on records, you -- could do something like this -- --
-- case charles of -- (match lastName -> "Dexter") -> Left False -- (match lastName -> "Ward") -> Left True -- (match (age, superior) -> (a, Just s)) -- | a > 18 -> Right a -- | otherwise -> Right (s .# age) -- -- :: Either Bool Int ---- -- Finally, a pair of combinators is provided to access record fields of -- collection types. The (#!) combinator has the type a -- :-> c b -> i -> a :-> Maybe b, where c is an -- instance of Indexable and i is an index type -- suitable for c. For example, you can use an -- Integral value to index a String and a -- value of type k to index a Map k v. The -- (#!!) combinator is also provided. It doesn't have -- Maybe in the return type, so using a bad index will usually -- result in an error. -- -- Currently, instances are provided for [a], -- Data.Map, Data.IntMap, -- Data.Array.IArray, Data.Set and -- Data.IntSet. module Data.Record.Field