ekmett / lens Goto Github PK
View Code? Open in Web Editor NEWLenses, Folds, and Traversals - Join us on web.libera.chat #haskell-lens
Home Page: http://lens.github.io/
License: Other
Lenses, Folds, and Traversals - Join us on web.libera.chat #haskell-lens
Home Page: http://lens.github.io/
License: Other
I've been considering adding a zipper module for some time. I hacked up @jberryman's zippo
library to use lens
.
Where to put it?
If it lived in Control.Lens.Zipper
it would be odd if it wasn't exported from Control.Lens
, (since I do re-export Control.Lens.*
for everything except Control.Lens.Internal
, but if it was exported from Control.Lens
it would take a lot of useful names that I've deliberately avoided touching because they are popular in end user code.
This would result in more code that can have exotic lenses (and traversals) constructed automatically
This would let us compile with an older transformers if we add back in the #ifdef blocks around uses of state.
It would be nice to reduce the name clutter for the common 'traverse the polymorphic argument' case.
Problem:
For Traversable the traverse is a valid traversal, but if itraverse is a valid indexed traversal, then its not usable as the function that traverses the container with the index directly.
It would be nice to port the Lensed
monad that I designed for snap
, perhaps modified to actually take a Zipper
rather than a Lens
.
Zipped?
See @orenbenkiki's comment in issue #49
Hopefully, the title is self explanatory. The constructor itself is exported, just not the corresponding lens.
We switched to a final encoding of Bazaar
because it made cloning dramatically faster. Would a final encoding of Context
have a similar performance impact? e.g.
newtype Store c d a = Store { runStore :: forall f. Functor f => (c -> f d) -> f a }
This might also speed up a number of Control.Lens.Plated
combinators.
It is possible to traverse an indexed container using a predicate on the index.
elements :: TraversableWithIndex i t => (i -> Bool) -> IndexedTraversal i (t a) (t b) a b
elementsOf :: (i -> Bool) -> InxexedTraversal i a b c d -> IndexedTraversal i a b c d
I'm sure that this has been pondered before, but I'd like to open a discussion about it. Simple lenses / traversals / isos are pretty common, so what if they had nice type operators?
These seem good:
type a :-> b = Simple Lens a b
type a :*> b = Simple Traversal a b
type a :<-> b = Simple Iso a b
type a :..> b = Fold a b
I was considering (:%>) for Lens, but decided on (:->) for nicer appearance and consistency with fclabels.
Not sure if I'd need these:
type a :^> b = Simple Getter b
type a :<~ b = Simple Setter b
And these'd be a little silly (but would still save parens!):
type (:@->) a b i = SimpleIndexedLens i a b
-- etc
I dunno about you, but I like these smileys - each variety is just a different nose!
When it was limited to folds we had a few more instances. It would be nice to handle more cases gracefully.
Now that we have unordered-containers is it worth it to revisit the decision not to have a class for At?
A tighter associativity version of % that can be interleaved with (^.).
Factored into a separate issue -- this was previously an offshoot of issue #17.
This would probably change the interface slightly.
Here is a possible approach for implementing makeClassy
for parameterized containers. It works without any type-specific logic, as long as all the types are functional dependencies of the container type. This is something I sorely need in my code...
Sample code:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Sample where
import Control.Lens hiding (Simple)
-- Unparameterized classy containers are easy (yey!)
data Trivial = Trivial { _trivialField :: Int }
makeClassy ''Trivial
trivialAdd :: HasTrivial container => Int -> container -> Int
trivialAdd int container = int + container ^. trivialField
data TrivialContainer = TrivialContainer { _trivialContainer :: Trivial }
makeLenses ''TrivialContainer
instance HasTrivial TrivialContainer where
trivial = trivialContainer
trivialExample :: Int -> TrivialContainer -> Int
trivialExample = trivialAdd
-- Parameterized containers seem possible:
data Simple num = Simple { _simpleField :: num }
makeLensesFor [ ("_simpleField", "__simpleField") ] ''Simple
-- Functional dependency to be compatible with the general case below.
class HasSimple container num | container -> num where
simple :: SimpleLens container (Simple num)
instance HasSimple (Simple num) num where
simple = id
simpleField :: forall container num . HasSimple container num => SimpleLens container num
simpleField = simple . __simpleField
simpleAdd :: forall container num . (HasSimple container num, Num num) => num -> container -> num
simpleAdd num container = num + container ^. simpleField
data SimpleContainer = SimpleContainer { _simpleContainer :: Simple Int }
makeLenses ''SimpleContainer
instance HasSimple SimpleContainer Int where
simple = simpleContainer
simpleExample :: Int -> SimpleContainer -> Int
simpleExample = simpleAdd
-- This even works for multiple types which become "phantom" for some fields,
-- as long as all types are functional dependencies of the container:
data Complex foo bar = Complex { _complexFoo :: foo, _complexBar :: bar }
makeLensesFor [ ("_complexFoo", "__complexFoo"), ("_complexBar", "__complexBar") ] ''Complex
-- The functional dependencies here are required for this approach to work.
class HasComplex container foo bar | container -> foo, container -> bar where
complex :: SimpleLens container (Complex foo bar)
instance HasComplex (Complex foo bar) foo bar where
complex = id
complexFoo :: forall container foo bar . HasComplex container foo bar => SimpleLens container foo
complexFoo = complex . __complexFoo
complexBar :: forall container foo bar . HasComplex container foo bar => SimpleLens container bar
complexBar = complex . __complexBar
complexAddFoo :: forall container foo bar . (HasComplex container foo bar, Num foo) => foo -> container -> foo
complexAddFoo foo container =
foo + container ^. complexFoo
complexAddBar :: forall container foo bar . (HasComplex container foo bar, Num bar) => bar -> container -> bar
complexAddBar bar container =
bar + container ^. complexBar
data ComplexContainer = ComplexContainer { _complexContainer :: Complex Int Int }
makeLenses ''ComplexContainer
instance HasComplex ComplexContainer Int Int where
complex = complexContainer
complexExample :: Int -> ComplexContainer -> Int
complexExample int container = complexAddFoo 1 container + complexAddBar 1 container
When you go to extract from a zipper, it is somewhat galling that defaulting doesn't work nicely due to the MPTC. I wonder if there is a nicer encoding that bypasses the need.
Using makeClassy
on:
data Foo = Foo { _fooX, _fooY :: Int }
in GHC 7.4.1, reportedly yields:
Main.hs:14:1:
Illegal type variable name: `'
When splicing a TH declaration:
class HasFoo t_0
where foo ::
fooX :: forall .
fooX = foo GHC.Base.. go_1
where go_1 _f_2 (Main.Foo __fooX'_3
__fooY_4) = (\__fooX_5 -> Main.Foo __fooX_5 __fooY_4) Data.Functor.<$> _f_2 __fooX'_3
fooY :: forall .
fooY = foo GHC.Base.. go_6
where go_6 _f_7 (Main.Foo __fooX_8
__fooY'_9) = (\__fooY_10 -> Main.Foo __fooX_8 __fooY_10) Data.Functor.<$> _f_7 __fooY'_9
TODO: Confirm this behavior and fix it. =)
Oren's wishlist included functions that can post-increment/multiply/append rather than pre-increment/multiply/append, showing us the old version rather than the new.
What semiotics would make sense for these operations? And can they be supported without adding undue complexity to the already baroque set of operators?
This was originally raised by @orenbenkiki in #36.
When we re-generalized Getting
we introduced a problem.
If you go to use (^.)
on a traversal with constraints on each argument, like, say, dynamic
then you now have to use an explicit type signature, because otherwise the b
and d
variables in Traversal a b c d
are totally unconstrained.
We need to come up with a work around.
...as you're aware ;-) But I didn't see the issue of partial lenses addressed in the docs or tutorial, so I thought opening a ticket would be best, if only as a place to address the issue once.
I want to build additional abstractions on top of lenses, but I don't find the current behavior of lens
w/r/t sum types to be acceptable. Here are my questions:
Applicative
or something (sorry, I'm still getting oriented to this lens flavor)I'm aware that the usual "lens laws" don't really permit lenses on types with multiple constructors, but that seems more of an argument for coming up with a better lens-like thing or better laws, rather than sort of ignoring the issue.
Capturing the relevant discussion on monadic traversals from #36.
@orenbenkiki commented an hour ago
As for the monadic modification application - intuitively, one would expect
that the order would be (1) getting the original value (2) invoking the
modification function to do whatever changes it wants and (3) taking the
final result and setting the value to it. In fact, I am hard pressed to
think of any other possible order. I agree things might get muddier if the
operator/function modifies several lensed locations inside the container;
here one could argue either for "modify one location at a time", so the
modification function of the 2nd location would see the updated 1st
location, or a more transactional "modify everything at once", where all
the modification functions see the original values. I think the
one-at-a-time approach is more consistent with the way the state monad
works in general, so I'd go with that; I also suspect that this would be
the "natural" semantics that would fall out of any reasonable code, but I
of course I didn't delve into the relevant source files.
I'm not certain what would be the ideal notation for a monadic %= / %%=
modification function. Perhaps |%= and |%%= (mnemonic: pipe the state
through the modification)?
I could whip up some patch code for |%=, |%%=, withValue and withModified
if you'd like... I'll fork the GIT repository and do all this the proper
way (which I should have done from the start, sorry about that).
Thanks again,
Oren Ben-Kiki
@orenbenkiki commented an hour ago
I think I understand the concern but it seems to me this applies only to
operations which work on multiple locations - indexed ones, at that.
That is, I am hard pressed to think of a case where using the simple
single-location '%=' and '%%=' with a monadic modification function would
cause the issues you describe, even when applied to a list (e.g., "list .
element 2 |%= monadicChangeElement").
Perhaps I am missing something...?
Oren.
…
ekmett commented an hour ago
Consider _last, and someone coming along and appending to the list in the meantime.
Or even _head when someone comes in and inserts something in the meantime, so the thing you based your updated head on is now shifted downstream.
ekmett commented an hour ago
These issues manifest themselves in other languages too. Many languages like c++ have iterators, but inserting into the container while iterating may or may not be legal, and leads to all sorts of confusing semantics and places where elements may be missed in the traversal.
orenbenkiki commented 32 minutes ago
Ouch. You are right, doing "list . _last |%= functionThatAppendsToList" would have ill-defined semantics. It is a pity to give up this very useful ability because of this edge case... but if we'd rather be safe than sorry, this kills "|%=" and "|%%=". If we'd rather maximize usability, I'm not so sure (people could be warned off this edge case). I guess leaving it out for now would be safest, this can always be re-visited in the future if there is sufficient demand for it.
ekmett commented just now
Going to close out the issue and leave it as a record of the discussion.
This will let us pipeline a lot of the makeLensesFor, etc. code more cleanly.
As it is, when you are given a lens/traversal that you plan to use to change the type of a field, it is hard to use any getting or fold combinators on it.
I think the best compromise is to keep Getter and Fold in their two argument form and add the other two args back to Getting and IndexedGetting.
This is pretty unimportant, but it seems like it'd be good to have a conventional contraction for traversal. The main purpose of these is to provide suffixes for function names.
If we arrange the core type names by length:
Iso
Lens
Fold
Action
Getter
Setter
Traversal
Projection
MonadicFold
Towards the end, they become very unwieldy suffixes. However, thankfully, contractions are pretty straightforward:
Traversal --> Trav
Projection --> Proj
MonadicFold --> MFold
However, "Trav" seems weird. "Walk" came to mind, but unless that becomes convention, I'll stick to "Trav". Thoughts?
-- allow them to index by ordinal position
indexed :: Traversable a b c d -> IndexedTraversable Int a b c d
indexed :: Foldable a b c d -> IndexedFoldable Int a b c d
This would let us 'reindex' another indexedtraversal by position in a result list as well, because every indexedtraversal is a traversal.
Embedding/projection pairs would be nice to have an analogue to:
data Embedding a b = Embedding (a -> b) (b -> Maybe a)
data Projection a b = Projection (a -> Maybe b) (b -> a)
with the ability to flip back and forth, and treat them like a a getter (or fold) and/or traversal/lens in the right situations?
I suppose the first step in knowing if an alternative implementation is warranted for #43 is to actually do some benchmarking.
[00:15] edwardk: Context is slow
[00:16] edwardk: maybe my fast Context comonad?
[00:16] edwardk: newtype Store c d a = Store { runStore :: forall f. Functor f => (c -> f d) -> f a }
[00:16] edwardk: i could probably use that
[00:17] edwardk: that should give back the speed lost if i come up with the right implementation
[00:17] edwardk: i'll add an issue to explore it
[00:19] mgsloan: what if it was "alongside l r = lens (\(a, a') -> (view l a, view r a')) (\(a, a') (b, b') -> (set l b a, set r b' a'))" orso
[00:20] edwardk: viewing and setting separately is probably not a win
[00:20] mgsloan: for big products it might be
[00:21] edwardk: but if you wanted to concoct an alongside benchmark i'd be happy to listen to it, and try my final Store on it as well
[00:21] mgsloan: especially if there's not much depth to l / r
[00:21] edwardk: feel like adding another benchmark to the benchmarks folder and benchmark suites?
[00:21] mgsloan: the final Store sounds good :) not sure I get it
[00:22] mgsloan: sure, could do that
Presently, libraries that want to provide lenses to users can do so without incurring a dependency on lens. This is a very neat property, but could be a little bit obtuse documentation-wise.
A small amount of discussion here: diagrams/diagrams-lib#49 (comment)
Also, as soon as libraries want to provide anything more than lenses and traversals (which are definitely the most common), they definitely need to add a dependency. This could encourage library writers to use lenses instead of the more specific iso, or traversal instead of the more specific projection.
Anyway, I really don't feel very strongly about it - just putting down thoughts. Diagrams is fine with adding a lens dependency, and I don't personally anticipate needing lens-core.
I think it would make sense to use $ instead of % where the mnemonic is for applying a function, given that () instead of (%), and so on (Control.Lens.Setter, Type, IndexedLens, and IndexedSetter seem to be affected). Is there a rationale for % besides it being an otherwise unused symbol?
If you agree I can prepare a patch.
(I'm guessing operators with more than one % like (%%=) and (%%) should also become () in that case, rather than keeping one of the %s?)
Intentional or not?
Trying the example from the source comments:
$ ghci
GHCi, version 7.4.2: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m + Control.Lens Data.Map.Lens Data.Map
Prelude Control.Lens Data.Map.Lens Data.Map> fromList [("hello",12)] ^. at "hello"
<interactive>:3:28:
No instance for (At [Char] (Map [Char]))
arising from a use of `at'
Possible fix:
add an instance declaration for (At [Char] (Map [Char]))
In the second argument of `(^.)', namely `at "hello"'
In the expression: fromList [("hello", 12)] ^. at "hello"
In an equation for `it':
it = fromList [("hello", 12)] ^. at "hello"
Prelude Control.Lens Data.Map.Lens Data.Map>
Leaving GHCi.
$ cabal list --installed --simple-output | grep lens
lens 2.6.1
BTW, what is the equivalent of "at" for accessing the n-th element of a list? I am sorely missing a "cheat sheet" that lists the common cases of using lenses, instead of hunting them across all the source files... In fact I was creating an HUnit test file that does nothing but list all these common cases, when I run into the above.
bar :: Functor f => (Int -> f Int) -> Foo a -> f Foo a
should be
bar :: Functor f => (Int -> f Int) -> Foo a -> f (Foo a)
15:08] edwardk: @messages
[15:08] lambdabot: nandsaid 1h 41m 20s ago: in the docs for ‘fromWithin’ you have fromWithin l = fromMaybe . within l; surely you mean fromJust? [15:08] edwardk: @tell nand
yes
[15:08] lambdabot: Consider it noted.
The idea of incorporating @mgsloan's lenq dsl directly came up at Boston Haskell.
We can't chain assignment with <~
.
typeVars
goes out of its way to avoid performing substitution of types bound by outer quantifiers.
substType
does not.
Setter
s are capable of replacing the target value(s) because they are capable of modifying the target value(s). Modification always allows replacement as a special case (via const
and its ilk), but you cannot generate modification from solely replacement (if you have both replacement and retrieval, you can, but then you just end up with a Traversal
).
This seems to hint at a supertype of Setter
: Replacer
(plus Indexed
and/or Simple
variants, of course). Essentially:
Getter a c
is isomorphic to a -> c
Setter a b c d
is isomorphic to (c -> d) -> a -> b
Replacer a b d
is isomorphic to d -> a -> b
I've come up with a couple of encodings for Replacer
that try to fit within the van Laarhoven model, and I'd like to generate some discussion of this. It's not critical to me, but it is an interesting hole in the current design space.
type Replacer a b d = forall f. (Settable f) => (forall c. c -> f d) -> a -> f b
type Replacer a b d = forall f. (Settable f) => (Void -> f d) -> a -> f b
Split out $setup blocks
While the documentation in Control.Lens.Fold states that
dropWhile p ≡ toListOf (droppingWhile p folded)
we see that
Prelude Control.Lens> toListOf (droppingWhile (<=3) folded) [1,6,1]
[6]
Prelude Control.Lens> dropWhile (<=3) [1,6,1]
[6,1]
src/Control/Lens/WithIndex.hs:316:1:
The multi-parameter class `TraversableWithIndex' cannot have generic methods
In the class declaration for `TraversableWithIndex'
This would facilitate exporting the lenses in the export list because the enduser could just export HasFoo(..)
@orenbenkiki submitted a hunit test suite in #36.
Get it to work.
In the following code snippet it would be convenient to generate lenses without explicit type signatures for the purpose of documentation:
data A = A { _a ∷ Int, _b ∷ Int }
makeLensesNoTypeSignatures ''A
So we can have nice haddocks:
-- | Lens 'a'
a ∷ Lens A A Int Int
-- | Lens 'b'
b ∷ Lens A A Int Int
We can detect when a field is missing from a constructor and emit 'pure' instead, and give a 'Traversal' type signature instead.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.