{"action":"created","issue":{"url":"https://api.github.com/repos/haskell/containers/issues/192","repository_url":"https://api.github.com/repos/haskell/containers","labels_url":"https://api.github.com/repos/haskell/containers/issues/192/labels{/name}","comments_url":"https://api.github.com/repos/haskell/containers/issues/192/comments","events_url":"https://api.github.com/repos/haskell/containers/issues/192/events","html_url":"https://github.com/haskell/containers/pull/192","id":143866412,"number":192,"title":"A lens-compatible `at`","user":{"login":"Rufflewind","id":6571068,"avatar_url":"https://avatars.githubusercontent.com/u/6571068?v=3","gravatar_id":"","url":"https://api.github.com/users/Rufflewind","html_url":"https://github.com/Rufflewind","followers_url":"https://api.github.com/users/Rufflewind/followers","following_url":"https://api.github.com/users/Rufflewind/following{/other_user}","gists_url":"https://api.github.com/users/Rufflewind/gists{/gist_id}","starred_url":"https://api.github.com/users/Rufflewind/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Rufflewind/subscriptions","organizations_url":"https://api.github.com/users/Rufflewind/orgs","repos_url":"https://api.github.com/users/Rufflewind/repos","events_url":"https://api.github.com/users/Rufflewind/events{/privacy}","received_events_url":"https://api.github.com/users/Rufflewind/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":12,"created_at":"2016-03-28T01:03:10Z","updated_at":"2016-04-28T23:08:11Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/haskell/containers/pulls/192","html_url":"https://github.com/haskell/containers/pull/192","diff_url":"https://github.com/haskell/containers/pull/192.diff","patch_url":"https://github.com/haskell/containers/pull/192.patch"},"body":"It works similar to `alter`, but more general (allows an arbitrary `Functor` instead of just `Identity`).  For doing an arbitrary combined `lookup` + `insert`/`delete` in a monad, this might be a little faster than doing them separately.\r\n\r\nDid some performance benchmarks with GHC 7.10.3 and as far as I can tell it works just as good as `alter` and `lookup` with the right choice of `Functor`.\r\n\r\n(It is slower by 50% if you try to use `(,) ()` instead of `Identity`. I have not figured out why this is the case − I guess this is harder to optimize out than a trivial wrapper.)"},"comment":{"url":"https://api.github.com/repos/haskell/containers/issues/comments/215588754","html_url":"https://github.com/haskell/containers/pull/192#issuecomment-215588754","issue_url":"https://api.github.com/repos/haskell/containers/issues/192","id":215588754,"user":{"login":"Rufflewind","id":6571068,"avatar_url":"https://avatars.githubusercontent.com/u/6571068?v=3","gravatar_id":"","url":"https://api.github.com/users/Rufflewind","html_url":"https://github.com/Rufflewind","followers_url":"https://api.github.com/users/Rufflewind/followers","following_url":"https://api.github.com/users/Rufflewind/following{/other_user}","gists_url":"https://api.github.com/users/Rufflewind/gists{/gist_id}","starred_url":"https://api.github.com/users/Rufflewind/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Rufflewind/subscriptions","organizations_url":"https://api.github.com/users/Rufflewind/orgs","repos_url":"https://api.github.com/users/Rufflewind/repos","events_url":"https://api.github.com/users/Rufflewind/events{/privacy}","received_events_url":"https://api.github.com/users/Rufflewind/received_events","type":"User","site_admin":false},"created_at":"2016-04-28T23:08:11Z","updated_at":"2016-04-28T23:08:11Z","body":"Assuming I did it right, the results aren't too promising unfortunately.  I didn't do any tests with a nontrivial Functor though, so I think it might win in those situations.\r\n\r\nI think I actually tried this experiment a while back but by directly passing the fmapped function around and combining them with `(.)`, which − if my understanding is correct − is what `Yoneda` does under the hood. (I didn't post the results because it was quite slow.)\r\n\r\nFrom a low-level perspective, composing functions with `(.)` (or indirectly via `Yoneda`) probably requires an allocation each time, so in effect we are manually reconstructing the call stack as a linked-list-like data structure. It would be more efficient if there was a way (like a GHC primitive) to capture a portion of the call stack as a function:\r\n\r\n~~~hs\r\nsetjmp :: (r -> a) -> b -> (r, a -> b)\r\n\r\nat :: (Functor f, Ord k) => k -> (Maybe a -> f (Maybe a)) -> Map k a -> f (Map k a)\r\nat k f m = g `fmap` f r\r\n  where\r\n\r\n    (r, g) = setjmp $ \\ longjmp -> go longjmp k m\r\n\r\n    go longjmp !k Tip = case longjmp Nothing of\r\n               Nothing -> Tip\r\n               Just x  -> singleton k x\r\n\r\n    go longjmp !k (Bin sx kx x l r) = case compare k kx of\r\n               LT -> balance kx x (go longjmp k l) r\r\n               GT -> balance kx x l (go longjmp k r)\r\n               EQ -> case longjmp (Just x) of\r\n                       Just x' -> Bin sx kx x' l r\r\n                       Nothing -> glue l r\r\n~~~\r\n\r\nIt's a rather unsafe combinator though, because the \"function\" it returns can only be called once – how would one enforce that?"}}