-
Notifications
You must be signed in to change notification settings - Fork 182
Add pop for sets and maps #1134
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
Comments
I don't really like the name |
What do you suggest?
This case is no different than, say, |
I like |
Just to add a quick cross‑language datapoint: the “remove‑and‑return” map operation is already called pop in several ecosystems—for example, Elixir’s Map.pop/3 and Python’s dict.pop. So the name wouldn’t be unusual; it would actually line up with what many developers already expect. |
"many developers" - that depends on who you ask. Many others would expect a function/a name in the vincinity of |
Nice, I wasn't aware of Elixir one.
I don't mind this, but they are too long when |
Lens has (<<%~) :: LensLike ((,) a) s t a b -> (a -> b) -> s -> (a, t) so (<<?~) :: LensLike ((,) a) s t a (Maybe b) -> b -> s -> (a, t) which basically does the opposite of what we want; I dunno why there isn't also a remove :: LensLike ((,) a) s t a (Maybe b) -> s -> (a, t) |
Some implementation notes: Using unpacked sums seemed like a good fit for the implementation, so here's an attempt. pop :: Ord k => k -> Map k a -> Maybe (a, Map k a)
pop k0 t0 = case unPopped' (go k0 t0) of
Popped y t -> Just (y, t)
NotPopped -> Nothing
where
-- go :: k -> Map k a -> (# (# a, Map k a #) | (# #) #)
go !k (Bin _ kx x l r) = case compare k kx of
LT -> case unPopped' (go k l) of
Popped y l' -> Popped' (Popped y (balanceR kx x l' r))
NotPopped -> Popped' NotPopped
EQ -> Popped' (Popped x (glue l r))
GT -> case unPopped' (go k r) of
Popped y r' -> Popped' (Popped y (balanceL kx x l r'))
NotPopped -> Popped' NotPopped
go !_ Tip = Popped' NotPopped
{-# INLINABLE pop #-}
data Popped' k a = Popped' { unPopped' :: {-# UNPACK #-} !(Popped k a) }
data Popped k a
= Popped a !(Map k a)
| NotPopped
Unfortunately, there is a problem here: when the sum is unpacked, GHC forgets that it is strict in the Map. This makes it check whether the Map is evaluated at every step. See GHC #25988. There is no simple fix for it. In this case, we can work around it by putting the Map in a strict pair, which doesn't suffer from the same problem. In the pop :: Ord k => k -> Map k a -> Maybe (a, Map k a)
pop k0 t0 = case go k0 t0 of
Popped (Just y) t -> Just (y, t)
_ -> Nothing
where
-- go :: k -> Map k a -> (# (# (# #) | a #), Map Int a #)
go !k (Bin _ kx x l r) = case compare k kx of
LT -> case go k l of
Popped y@(Just _) l' -> Popped y (balanceR kx x l' r)
p -> p
EQ -> Popped (Just x) (glue l r)
GT -> case go k r of
Popped y@(Just _) r' -> Popped y (balanceL kx x l r')
p -> p
go !_ Tip = Popped Nothing Tip
{-# INLINABLE pop #-}
data Popped k a = Popped {-# UNPACK #-} !(Maybe a) !(Map k a)
Much better. |
I like the name |
I propose adding
Returns a
Nothing
if the key is absent. Otherwise, returns a new set or map with the key removed, and the value in case it is a map.Previously proposed in #757 (with
Maybe
on the inside). I agree with the reasons there.Some more reasons:
k -> Map k a -> Maybe (a, Map k a)
is a closer inverse ofinsert
thandelete
.updateLookupWithKey (\_ _ -> Nothing)
is inefficient because 1. it takes a function which is unnecessary and 2. it always constructs a new map/set.Previously, there were some concerns about whether this is a user-demanded feature on the libraries thread (e.g. here), and the answer is yes! Hackage Search reveals quite a few results: https://hackage-search.serokell.io/?q=%5C.updateLookupWithKey%5Cs. Some examples:
I expect there are more instances doing
lookup
+delete
, and it's harder to search for those.Naming
Some suggestions were:
pop
- seems fine to meextract
- doesn't make much sense for setslookupDelete
- long name, also doesn't make much sense for sets. Sets would needmemberDelete
.Implementation
There are two ways to implement this.
alterF
style, then delete only if the key was present."Simple" should be faster in case the key is present, "Complicated" should be faster in case the key is absent. I think the simple way should be fine. Either way, unlike
updateLookupWithKey
, we don't construct a map/set if the key is absent.The text was updated successfully, but these errors were encountered: