Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"at" example doesn't work #36

Closed
orenbenkiki opened this issue Sep 8, 2012 · 13 comments
Closed

"at" example doesn't work #36

orenbenkiki opened this issue Sep 8, 2012 · 13 comments
Assignees

Comments

@orenbenkiki
Copy link

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.

@orenbenkiki
Copy link
Author

Turns out I had containers-0.5.0.0 on the system which messed things up. Unregistering it solved the problem.

@ekmett
Copy link
Owner

ekmett commented Sep 8, 2012

You appear to have multiple versions of containers installed. lens probably built with 0.4, but you have containers 0.5 installed and registered. You can either cabal install --reinstall lens to get it to pick the newer version of containers, or ghc-pkg unregister containers-0.5

@orenbenkiki
Copy link
Author

Yes, I figured the containers version issue following your response on IRC.
Thanks!

I created https://gist.github.com/3680166 - it contains two files.

One contains utilities which don't currently exist in the lens package, and
which you may consider including. These utilities include:

  • A .: operator allowing saying things like record' = record .: field +~ 2
  • << variants for some < operators (<<.~, <<.= etc.) which access the
    original value before the operations (if this is accepted, << variants
    would need to be added to all < operators, and there are a lot of these...)
  • Specialized operators: ++ (<> specialized for lists), valueFor
    (transverseAt specialized for maps).

The second file contains a suite of HUnit tests which demonstrate using the
lens functions in common use cases. I am hoping that arranging the tests in
terms of "what we are trying to achieve" rather than by "which lens
function is being used" would make it clearer to my developers. The tests
do use the utilities in the first file.

My motivation in creating this second file was basically capturing my
understanding of the lens package so far, which is pretty rudimentary at
this point. Like I said on IRC, I get the feeling that the lens package
would allow me to refactor a lot of code into much cleaner style, if I only
knew how to apply it correctly. I hope "someone" takes the (significant)
time of writing up a detailed tutorial of all the available lens features;
in the meanwhile, I'll keep updating my tests as my understanding grows.

Speaking of my understanding, a quick question - inside the state monad,
how would I do the equivalent of %= or %%= when the modification function
is itself monadic rather than pure? I saw a lot of promising leads in the
package but couldn't find an easy answer (possibly because it is 01:30AM
here right now :-).

At any rate, many thanks for this package, it certainly looks like this is
the way Haskell should be moving forward for records and deep data
structures in general.

Thanks,

Oren Ben-Kiki

On Sat, Sep 8, 2012 at 5:55 PM, Edward A. Kmett notifications@github.comwrote:

You appear to have multiple versions of containers installed. lensprobably built with 0.4, but you have
containers 0.5 installed and registered. You can either cabal install
--reinstall lens to get it to pick the newer version of containers, or ghc-pkg
unregister containers-0.5


Reply to this email directly or view it on GitHubhttps://github.com//issues/36#issuecomment-8388303.

@ekmett
Copy link
Owner

ekmett commented Sep 8, 2012

I'll definitely add the .:-like operator. probably as $>> given the precedence it should have and the way it reads when chained. The only question is really what module it should live in.

I've already implemented the ++~, etc. operators and they definitely will be in 2.7.

That said, I'm not entirely sold on adding the << variants of the combinators given that you can implement them with the existing %%~ combinators and the semiotics of < was simply that it was returning the value to the left. Perhaps if I can come up with an appropriate notation that doesn't break the meaning of the existing combinators. Putting the < on the right hand side of the operator might have the desired notational effect. e.g. ++<~ but it risks muddling the already enormous set of symbols we're exporting by forcing the user to mentally split the operator in a different place. =/

The problem is when you go to implement a 'monadic' %%= what should happen to the intermediate edits? you have monadic effects, those probably want to access or edit part of your state as well, so the order of events and meaning when you overwrite portions of the existing state in the current monad are somewhat ill posed.

There is a <~ operator that can assign the result of a monadic action to a lens though. This limited scenario works because the edit takes place after the action completes so there is a clear phase separation.

@ekmett
Copy link
Owner

ekmett commented Sep 9, 2012

I added .: as |> so it matched the use in scala and f#

@ekmett
Copy link
Owner

ekmett commented Sep 9, 2012

@orenbenkiki: I split off adding |>, <<++~, and the hunit test harness into separate issues as #37, #38 and #39 respectively.

@orenbenkiki
Copy link
Author

Thanks for integrating the code!

Yes, using |> is the same as F#, I should have thought of that. It is
terribly useful.

I agree making sense into the operator mnemonics is important, especially
since there are so many of them. Ideally <op~/<op= would return the old
value and the simple op~/op= to return the new value. This would be
consistent with the way that op= behaves in other programming languages.
However, this can only work for op=, because op~ must return the complete
updated container, so I can see why you used something else, so I can see
why you used <op~/<op=.

I can see how it would be consistent to say <+= and +<=, and I briefly
toyed with the idea myself, but like you said I got result was too visually
strange, since the "<" appears somewhere my eyes weren't expecting it. I
settled on <<+= as a compromise which I think works reasonably well.
Mnemonically, I think of "<<" as being "faster" than "<", so "<<" gets the
old value before the operator is applied and "<" takes the new value after
the operator is applied. I admit is is somewhat of s stretch :-)

Whether this is important enough a use case to warrant an operator, well...
I do have code in the state monad that does a lot of:

oldValue <- field <<%= someModification
... run code with the new value ...

field .= oldValue

I suppose I should just wrap this in a generic function along the lines of:

withValue field newValue $ do ... run code with the new value
withModified field someModifications $ do ... run code with the new

value

One would need to add the withValue_ and withModified_ variants that always
return () regardless of what the action does, along the lines of when vs.
when_, mapM vs. mapM_ and so on. Anyway... these would be cool additions,
don't you think? But even with them, this don't solve the problem for the
pure case. I guess I'm greedy wanting both options :-)

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

On Sun, Sep 9, 2012 at 3:27 AM, Edward A. Kmett notifications@github.comwrote:

@orenbenkiki https://github.com/orenbenkiki: I split off adding |>,
<<++~, and the hunit test harness into separate issues.


Reply to this email directly or view it on GitHubhttps://github.com//issues/36#issuecomment-8399402.

@ekmett
Copy link
Owner

ekmett commented Sep 9, 2012

The problem is that there isn't a good way to overload the monadic traversal of your state so it edits one location at a time.

You don't wind up 'repacking' the resulting state into a bundled up state until you're finished traversing all of the locations. =/

Also, intermediate actions may change the number of places that would be valid targets.

Consider what happens when you have a Traversal of a list that comprises your state.

There is no valid answer for how to tackle this one. The monadic action might remove entries, replace the list entirely, etc.

The reason this combinator doesn't exist is because it can't. ;)

It can exist in a limited lens-only form, but I have to admit that isn't a very satisfying combinator, because even that one doesn't necessarily have sensible laws for it.

It can legally move the target of the lens, so reading, updating state and writing back can well write back to an entirely different location than was read from. Consider a lens into the last location of a list.

I'm sorry I'm such a stickler for laws, but its the only way to keep the package sane.

Regarding the <<-combinators, I pushed the reply over to #39, to help me keep track of what changes are still open for consideration. ;)

@orenbenkiki
Copy link
Author

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.

On Sun, Sep 9, 2012 at 2:28 PM, Edward A. Kmett notifications@github.comwrote:

The problem is that there isn't a good way to overload the monadic
traversal of your state so it edits one location at a time.

You don't wind up 'repacking' the resulting state into a bundled up state
until you're finished traversing all of the locations. =/

Also, intermediate actions may change the number of places that would be
valid targets.

Consider what happens when you have a Traversal of a list that comprises
your state.

There is no valid answer for how to tackle this one. The monadic action
might remove entries, replace the list entirely, etc.

The reason this combinator doesn't exist is because it can't. ;)

It can exist in a limited lens-only form, but I have to admit that isn't a
very satisfying combinator, because even that one doesn't necessarily have
sensible laws for it.

It can legally move the target of the lens, so reading, updating state and
writing back can well write back to an entirely different location than was
read from. Consider a lens into the last location of a list.

I'm sorry I'm such a stickler for laws, but its the only way to keep the
package sane.

Regarding the <<-combinators, I pushed the reply over to #39#39,
to help me keep track of what changes are still open for consideration. ;)


Reply to this email directly or view it on GitHubhttps://github.com//issues/36#issuecomment-8402780.

@ekmett
Copy link
Owner

ekmett commented Sep 9, 2012

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
Copy link
Owner

ekmett commented Sep 9, 2012

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
Copy link
Author

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
Copy link
Owner

ekmett commented Sep 9, 2012

Going to close out the issue and leave it as a record of the discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants