I just uploaded the first version of a lens library I’ve been working on, called
yall. You can get it with a
cabal install yall
or check it out on github. There will be a
Template Haskell library for automatically deriving lenses at some point in the
future.
I was motivated primarily by the desire for a lens that is acceptable for
pez (existing libs such as the
excellent fclabels
or data-lens
didn’t fit the bill for assorted reasons), and
to explore some abstractions and generalizations re lenses more deeply.
The result is fairly rough at this point, but I’m interested in feedback.
Distinguishing features
This is a bit of copy/paste from the docs I just wrote.
Lenses are parameterized over two Monads (by convention m and w), and look
like a -> m (b -> w a, b). this lets us define lenses for sum types, that
perform validation, that do IO (e.g. persist data to disk), etc., etc.
a module Data.Yall.Iso that complements Lens powerfully
a rich set of category-level class instances (for now from
categories) for
Lens and Iso. These along with the pre-defined primitive lenses and
combinators give an interface comparable to Arrow
Examples
And here is a little showcase of functionality. First, an illustration of
partial lenses, appropriate for multi-constructor types.
123456789101112131415161718192021222324
-- First, lenses for a sum type. This is something like what we'll generate-- with template haskell.dataTesta=C1{_testString::String,_testA::a}|C2{_testString::String,_testRec::Testa}-- pure lens, polymorphic in Monad for composability:testString::(Monadw,Monadm)=>Lenswm(Testa)StringtestString=Lens$\t->return(\s->returnt{_testString=s},_testStringt)-- lenses that can fail. For now, use Maybe. In the TH library, I'll probably-- generate lenses polymorphic in <http://hackage.haskell.org/package/failure>testA::LensMMaybe(Testa)atestA=Lensfwheref(C1sa)=return(return.C1s,a)f_=NothingtestRec::LensMMaybe(Testa)(Testa)testRec=Lensfwheref(C2si)=return(return.C2s,i)f_=Nothing-- conposing a pure and partial lens:demo0::MaybeStringdemo0=getM(testString.testRec.testRec)(C2"top"(C1"lens will fail"True))
Now an example of a more creative use of monadic getter, allowing us to define
a lens on the “Nth” element in a list, returning our results in the [] monad
environment.
12345678910
-- Here we have a lens with a getter in the list monad, defining a mutable view-- on the Nth element of the list:nth::LensM[][a]anth=Lens$foldrnthGS[]wherenthGSnl=(return.(:mapsndl),n):map(prependn)lprepend=first.fmap.liftM.(:)-- This composes nicely. Set the Nth element of our list to 0:demo1::[[(Char,Int)]]demo1=setM(sndL.nth)0[('a',1),('b',2),('c',3)]
Finally here’s a bit of a silly example illustrating a lens with Monadic setter
(w) that does IO, in this case persisting a serialized version of the data
we’re operating on to a text file.
-- persist modifications to a type to a given file. An effect-ful identity lens.persistL::(Monadm)=>FilePath->LensIOmStringStringpersistLnm=Lens$\s->return(\s'->writeFilenms'>>returns',s)-- we'll use this one:tmpFile="/tmp/yall-test"printFileContents=putStrLn.("file contents: "++)=<<readFiletmpFile-- build a lens with some pre-defined Iso's that offers a [Int] view on a-- string that looks like, e.g. "1 2 3 4 5":unserializedL::(Monadw,Monadm)=>LenswmString[Int]unserializedL=isoL$ifmap(inverseIshowI).wordsI-- now add "persistence" effects to the above lens so everytime we do a "set"-- we update the file "yall-test" to redlect the new type.unserializedLP::(Monadm)=>LensIOmString[Int]unserializedLP=unserializedL.persistLtmpFiledemo2::IO()demo2=do-- apply the lens setter to `mempty` for some Monoid ([Char] in this case)str<-setEmptyWunserializedLP[1..5]-- LOGGING: the string we got above (by setting [Int]) was written to a file:printstrprintFileContentsstr'<-modifyWunserializedLP(map(*2).(6:).reverse)str-- LOGGING: now the file was modified to reflect the changed value:printstr'printFileContents
Future
I still need to create TH deriving functionality for the package, and will
announce when that happens. Been busy lately so I’m not sure when I’ll get to
it, but let me know your questions/comments/concerns and I’ll try to address
them promptly.