
Hello, I'm pleased to announce the release of my new library, named "has", written to aim to ease pain at inconvinience of Haskell's build-in records. With the has, You can reuse accessors over records to write generic function, combine records with another. Repository is at GitHub: http://github.com/nonowarn/has Uploaded on Hackage: http://hackage.haskell.org/package/has So you can install this by "cabal install has" You can use the has in three steps (without counting installation). 1. Write {-# OPTIONS_GHC -fglasgow-exts #-} top of your code, import Data.Has module.
{-# OPTIONS_GHC -fglasgow-exts #-} import Data.Has
2. Define entities. "Entity" is data to index field in records. You can define an entity in one line.
data Foo = Foo; type instance TypeOf Foo = Int
(I lied) Before semicolon, declares entity. After semicolon, specifies the type to which the entity points. Define some entities for later examples.
data Bar = Bar; type instance TypeOf Bar = Double data Baz = Baz; type instance TypeOf Baz = String data Quux = Quux; type instance TypeOf Quux = Bool
3. Define Records by concatinating fields of entities.
type MyRecord = FieldOf Foo :&: FieldOf Bar :&: FieldOf Baz
This is almost same as writing < data MyRecord = MyRecord { foo :: Int < , bar :: Double < , baz :: String < } To construct a value of record, remove colons and replace entities in record with values, and uncapitalize some words.
aRecord :: MyRecord aRecord = fieldOf 42 & fieldOf 3.14 & fieldOf "string"
And you can play with it. To read/write/modify a value of field in records, you can use functions with names stealed from data-accessor. But uses value-level entities instead of accessors. < Foo ^. aRecord -- Reading < Foo ^= 4649 $ aRecord -- Writing < Foo ^: (*2) $ aRecord -- Modifying If we have another record type contains Foo field, You can still access the field in the same way.
type AnotherRecord = FieldOf Bar :&: FieldOf Foo anotherRecord :: AnotherRecord anotherRecord = fieldOf 2.71 & fieldOf 31
< Foo ^. anotherRecord -- And this also works Using these functions and Has constraint, You can write generic functions over records.
fooIsGreaterThan :: (Has Foo r) => r -> Int -> Bool fooIsGreaterThan r x = (Foo ^. r) > x
< aRecord `fooIsGreaterThan` 40 -- evaluated to True < anotherRecord `fooIsGreaterThan` 40 -- evaluated To False Even if you defined another record by combining records by (:&:), you can still access the field, and apply to generic functions.
type MoreRecord = FieldOf Baz :&: FieldOf Quux type CombinedRecord = AnotherRecord :&: MoreRecord combinedRecord :: CombinedRecord combinedRecord = (fieldOf 1.618 & fieldOf 39) & (fieldOf "sowaka" & fieldOf True) -- We can omit parentheses -- (even place parens anyware in record)
< combinedRecord `fooIsGreaterThan` 40 -- This yet works The Has constraint provides not only genericity but also safety. If the record doesn't satisfy the constraint, the type checker rejects it.
predicateOnRecords :: (Has Foo r, Has Quux r) => r -> Bool predicateOnRecords r = fooIsGreaterThan r 30 && (Quux ^. r)
< predicateOnRecords combinedRecord -- This is OK < predicateOnRecords aRecord -- This yields compile error More examples included in package[1] [1]: http://github.com/nonowarn/has/tree/master/examples/ This library is inspired by HList[2], and interfaces are stealed from data-accessors[3]. And lenses[4], fclabels[5], and records[6] devote themselves to similar purposes. [2]: http://hackage.haskell.org/package/HList [3]: http://hackage.haskell.org/package/data-accessor [4]: http://hackage.haskell.org/package/lenses [5]: http://hackage.haskell.org/package/fclabels [6]: http://hackage.haskell.org/package/records Enjoy! -nwn