-
Notifications
You must be signed in to change notification settings - Fork 24
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
if map contains key then error else add key/value to map #15
Comments
One thought is that map = map.entry(someKey)
.patMat( kvPair -> map,
() -> map.assoc(someKey, v) ) You are using == (I assume in Java, because it means something different in Kotlin) which is referential "equality" with Objects (and value equality with primitives). You're really asking, "Do these two objects share the same address in memory?" as a test for whether they are equal or not. This is the default implementation of Object.equals(). Actually, assoc() uses this same definition of equality: This will work for Strings that are compiled into the program (because the compiler de-duplicates Strings), enums, and primitives (which are actually compared by value because they have no address). But you can run into problems with any other kind of object. For instance, Rich Hickey used referential equality in PersistentHashMap as a speed and memory optimization. It works for that, but it's not a substitute for a true equality test based on the important values in an object. I'm guessing a bit here. If I'm not answering your question, you might try writing a function (even a poor one) that does what you want. Submit that in your response. Just so that I can see the context of what you're doing. I don't mean to extend PersistentHashMap, just write a function that takes a map and returns either a Map (if you want to ignore duplicates) or an |
I think something like Map<K,V> put(K key, U value, BiFunction<? super V,? super U,? extends V> merge) as defined in javaslang Map interface http://www.javadoc.io/doc/io.javaslang/javaslang/2.1.0-alpha or public TreeMap<K,V> update(K k, F<V,V> f, V v) From http://www.functionaljava.org/javadoc/4.7/functionaljava/index.html would allow what I meant. |
Thank you for taking the time to explain this. I'm including direct links to your specific examples just so I can reference them very quickly in the future. FunctionalJava: Let me see if I understand this correctly. Both methods return a new Map. If the old map contains the key, a function is executed and the result stored as the new value in the map (the result could be the old value unchanged). This function takes the old value associated with the key (and maybe the new value as well). Is that correct? How are you using this feature? You mentioned assocIfAbsent. If I were to implement that in JavaSlang, it would look like this: map = map.put(key, newVal, (oldVal, newVal) -> oldVal); In FunctionalJava: map = map.update(key, newVal, (oldVal) -> oldVal); In Paguro: // get a Paguro Option of the k/v pair associated with this key.
map = map.entry(key)
// if isSome() return the map unchanged.
.patMat( oldKV -> map,
// if isNone(), assoc the new value.
() -> map.assoc(key, newVal) ) ML, Scala, and Haskell allow matching based on types. It's a little bit like destructuring in a way. Paguro's Option is intended to be similar to that. OneOf2 works the same way and it will probably be joined by OneOf3, OneOf4, ... in the future. Those might be renamed Union2, Union3, etc. because they can function like Union types for Java. But I digress... Your other example was JavaSlang: map = map.put(key, newVal, (oldVal, newVal) -> throw new RuntimeException("contains")); FunctionalJava: map = map.update(key, newVal, (oldVal) -> throw new RuntimeException("contains")); Paguro: map = map.entry(key)
.patMat( oldKV -> throw new RuntimeException("contains"),
() -> map.assoc(key, newVal) ) So, FunctionalJava is the briefest and Paguro is the most verbose for this use case. I haven't personally seen a lot of use for assocIfAbsent(key, value). I usually am fine with letting the map take the last value that was put in it. So it doesn't seem a strong candidate for a convenience method. But perhaps the way you use maps will convince me otherwise? Hmm... You know Paguro's ImList has a reverse()? If you reverse your input, then let the map take the last value, the result is the same as what I think you're asking for. ImList<User> users = ...; // however you get this data
ImMap<Long,User> usersById =
users.reverse()
.toImMap(u -> tup(u.id, u)); By reversing the input, the earliest values effectively overwrite the later ones. I still don't feel like I completely understand your use model. I hope this is helpful. |
I am using it like a database primary index. First key wins, exception on duplicates. if (!map.containsKey(key)) // first walk For HashMap that was fixed with putIfAbsent. The difference between your patMap example is the extra option object and the for me strange Result: I want ImMap.patMap ;-) |
Paguro generally accommodates what you are asking for with patMat ("pattern matching"). I understand that you don't like creating a tuple, or the syntax of accessing the map inside the match. You are asking for a convenience method for your specific use case which I think is somewhat rare. Unless/until more people ask for it, I can't see putting it ahead of the other development priorities listed here: So, "maybe someday" is the current resolution of your issue. I'm just going to rename this issue and leave it open for a while to see if more people vote for it. |
I think I have new insight into what you might be doing. You must have some source of data to add to this map. Let's assume it's from a java.util.ArrayList, called ImMap<String,Integer> foo(ArrayList<String> someList,
Fn1<String,Integer> f) {
return xform(someList).toImMap(x -> tup(x, f.apply(x)));
} Maybe you would add xform(someList)
.fold(map(),
(accum, s) -> accum.entry(s)
.match(exists -> {throw new Exception("Duplicate");},
() -> accum.assoc(s, f.apply(s))));
Yay! But many functional programmers don't like exceptions. Especially to cause loop termination. If you want to return an Or<ImMap<String,Integer>,String> baz(ArrayList<String> someList,
Fn1<String,Integer> f) {
return xform(someList)
.foldUntil(map(),
(accum, s) -> accum.containsKey(s) ? null
: "Duplicate key",
(accum, s) -> accum.assoc(s, f.apply(s)));
} Ahh... purely functional error handling. The first <G,B> Or<G,B> foldUntil(G ident,
Fn2<? super G,? super T,B> terminator,
Fn2<? super G,? super T,G> reducer); The G means Good, B means Bad, T is the type of the input items from the collection you're processing. This will work and be fast if it solves your issue. If you actually had to throw the exception later for some reason, you could: ImMap<String,Integer> doTheBaz(ArrayList<String> someList) throws Exception {
return baz(someList, String::length)
.match(good -> good,
bad -> {throw new Exception(bad);});
} I need to think about all the implications, but I like it and already coded it into Paguro 3.0. I think it will stay there, so please let me know if it's not right. QuestionsQ: Why are you returning null? I thought you didn't like null? Q: What else is in 3.0? When will it be out? Thanks for your interest. I hope you can continue to enjoy using Paguro! |
I think you have given up, which is OK. But for the record, this is now a part of Paguro 3.0 here: |
No need to backport. |
I fixed your issue as I understood it, but maybe not as you understood it. Not sure whether that means this should be closed as "fixed" or "wont fix" but it sounds like time to close it either way. I feel you have helped the development of Paguro and I thank you! |
Moin,
while implementing a primary index with ImMap i fell fack into mutable habits:
if(contains) then error else add
in my case the bias is on keys being unique and so i can just add the new key and see if the map
instance changes.
That assumes that the following holds true:
map.assoc(existingKey, v) == map
If that holds true i wonder if assocIfAbsent is useful to add.
May take a value or a supplier.
Opinions?
Cal
The text was updated successfully, but these errors were encountered: