-
Notifications
You must be signed in to change notification settings - Fork 185
A lens-compatible at
#192
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
A lens-compatible at
#192
Conversation
Akin to `alter` but allows an arbitrary Functor.
Hi, thanks for the patch, including bechmarks and tests! I am not totally convinced about performance of
There are four "patterns" of using
I think it is harmful to wrap each recursion of If you want to pursue this, we need to make sure the performance is reasonable. Therefore please add additional benchmarks -- take implementation of Also, if we are going to add |
BTW, the benchmarks may of course show that I am not right and that your |
I've added comparisons with the existing lens implementation.
I would compare Comparing against the existing Lens implementation of
I'm not entirely sure why this is the case. In constrast, It'd be nice if there was some clever way to avoid the repeated |
Thanks for the benchmarks. On my machine (GHC 7.6, I use Debian Stable), the benchmarks from 584ce4b give the following results:
Here, the
Now
Once again, Altogether, you can see that With this performance profile, |
I think the I personally think (at least based on my benchmarks) it's kind of a mixed bag: better in some situations but worse in others. I can't really think of a way to improve it – if only there was some way to capture the call stack efficiently and do an |
The problem is that the new implementatioin is better only for trivial functors -- but I would expect the use case to be some nontrivial functors (IO, lists, Maybe, ...). Therefore, the cost of traversing the path to the element in question twice is better than allocating each modified subtree in the functor. Note that even if we traverse the path twice, the first it "read-only" lookup which is an order faster that the second where we rebuild the tree if required. |
That's certainly true and believable for integers. But what about data types with nontrivial |
That is of course right. However, I would hope that for types with costly Generally, I prefer less allocations when I choose between allocations vs time, but it is more a personal oppinion. It is unfortunate though that we cannot have "the best" from the both implementations -- traverse the tree only once, and allocating only two values in the functor (the user functions and the complete results). If you had an idea how to achive that, do not hesitate to tell me :-) But we would need to use the stack for representing the traversed path, using some kind of explicit zipper will be bad for performance. |
@Rufflewind, do you think you could benchmark this with a atCoyoneda :: Ord k =>
k -> (Maybe a -> Coyoneda f (Maybe a)) -> Map k a -> Coyoneda f (Map k a) and write |
Actually... |
Assuming I used Yoneda properly, here are the benchmarks for f7bb3ab
|
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. I think I actually tried this experiment a while back but by directly passing the fmapped function around and combining them with From a low-level perspective, composing functions with setjmp :: ((r -> a) -> b) -> (r, a -> b)
at :: (Functor f, Ord k) => k -> (Maybe a -> f (Maybe a)) -> Map k a -> f (Map k a)
at k f m = g `fmap` f r
where
(r, g) = setjmp $ \ longjmp -> go longjmp k m
go longjmp !k Tip = case longjmp Nothing of
Nothing -> Tip
Just x -> singleton k x
go longjmp !k (Bin sx kx x l r) = case compare k kx of
LT -> balance kx x (go longjmp k l) r
GT -> balance kx x l (go longjmp k r)
EQ -> case longjmp (Just x) of
Just x' -> Bin sx kx x' l r
Nothing -> glue l r It's a rather unsafe combinator though, because the "function" it returns can only be called once – how would one enforce that? |
@Rufflewind, I'd very much like to have some implementation of this operation to deal with keys that are somewhat more expensive than |
Hrmm. No, one word is not enough for the biggest maps, but it is big enough for fairly large ones. Either we could use a two-word implementation (perhaps based on ideas in |
@Rufflewind, I fleshed out the bit queue idea in #215. It seems to give fairly decent performance. That said, I am a bit-twiddling novice and my horrible code could probably be made substantially faster by someone who knows more about such things. Do you think you could take a look? |
If we have a W-bit address space (2^W addressable bytes), and each node requires 3W/8 bytes, then there can be at most N=2^W/(3W/8) nodes.
According to this, the largest and most imbalanced tree would have 3N/4 nodes on the left and N/4 nodes on the right. Applying this recursively, one obtains the approximate equation
where H is the height. Hence:
which is ≈140 for 64-bit and ≈65 for 32-bit. (The ratio stays between 2 and log(4/3)≈2.41.) |
Even 64-bit machines don't have 64-bit address spaces (I think maybe 48 or If we have a W-bit address space (2^W addressable bytes), and each node We maintain an invariant that size l <= 3 * size r According to this, the largest and most imbalanced tree would have 3N/4 N (3/4)^H = 3 where H is the height. Hence: H = log(2^W/(9W/8))/log(4/3) which is ≈140 for 64-bit and ≈65 for 32-bit. (The ratio stays between 2 and — Reply to this email directly or view it on GitHub |
It works similar to
alter
, but more general (allows an arbitraryFunctor
instead of justIdentity
). For doing an arbitrary combinedlookup
+insert
/delete
in a monad, this might be a little faster than doing them separately.Did some performance benchmarks with GHC 7.10.3 and as far as I can tell it works just as good as
alter
andlookup
with the right choice ofFunctor
.(It is slower by 50% if you try to use
(,) ()
instead ofIdentity
. I have not figured out why this is the case − I guess this is harder to optimize out than a trivial wrapper.)