Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
126 lines (84 sloc) 4.04 KB
(This is a literate haskell style tutorial of the has library, based
on the announce post to haskell-cafe)
I'm pleased to announce the release of my new library, named "has",
written to aim to ease pain at inconvinience of Haskell's built-in
records.
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"
With the has, You can reuse accessors over records to write generic
function, combine records with another.
You can use the has in three steps (without counting installation).
1. Write a pragma to enable some extensions at the top of your code,
import Data.Has module.
> {-# LANGUAGE TypeFamilies,TypeOperators,FlexibleContexts #-}
> import Data.Has
If you are lazy, you may prefer
< {-# OPTIONS_GHC -fglasgow-exts #-}
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
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