Dumb program to illustrate this point:
class Dummy:
def __init__(self, value): self.value = value
def __repr__(self): return 'Dummy(%s)' % self.value
def __hash__(self): return 0
def __eq__(self, other): return True
a = {Dummy(0)}
b = {Dummy(1)}
print(a | b)
print(b | a)
print(a | b == b | a)The comparison was to say, "this decision is difficult everywhere" -- which lang seems beside the point.
d1 | d2 | d3 | ...
is equivalent to: {**d1, **d2, **d3, ...} d1 | d2
should be because losing information/desirable properties/weird errors”, read “it’s not super obvious what {**d1, **d2}
should be because losing information/desirable properties/weird errors”.Except I guess one could throw in something about TOOWTDI too.
{**d1, **d2}
In other words, there are no new semantics to discuss here. I'm just saying the two syntaxes should be equivalent.Since this is a new operator, that shouldn’t be an issue.
I think losing commutivity is okay. After all, d1.update(d2) != d2.update(d1) if keys conflict.
Secondly I claim that the issue with using | is that it is not a new operator. It is a new, incompatible meaning for an old operator. Old code might not bother checking that its arg is a set because of it weren’t a set then | or in would fail. New programmers might see dicts as being basically sets and wrongly assume functions for sets would correctly work on dicts.
d1 = {'a': 1}
d2 = {'a': 2}
d3 = {**d1, **d2, add_func)
def add_func(a, b):
return a+b
Or something along those linesI assume the same key with the same value would be OK, but I'm not really sure it's a good idea for it to be OK.
a & b = { k: (a[k], b[k]) for k in a.keys() | b.keys() }
a | b = { k: (a.get(k, None), b.get(k, None)) for k in a.keys() | b.keys() }
a |& b = { k: (a.get(k,None), v) for k, v in b.items() }
a &| b = { k: (v, b.get(k,None)) for k, v in a.items() }
a |_| b = { k: only(a,b,k) for k in a.keys() | b.keys() }
def only(a,b,k):
if k in a && k in b:
throw DuplicateKey(a,b,k)
elseif k not in a && k not in b:
assert(false)
elseif k in a:
return a[k]
else:
return b[k]
This doesn’t work well if values can be None so maybe instead of pairs there should be objects Left(x), Right(y), and Both(x,y) {**d1, **d2}
idiom is just a clever mashup of Python's dictionary construction literal {}, and * * unpacking. That's why it only works with string-valued keys (which is a major limitation).Adding a third item to the dictionary literal would require special-casing the {} dictionary construction literal.
>>> { 'a' : 1 } | { 'a' : 2 }
ISTM the most logical result would be: { 'a' : { 1, 2 } }
...but I could certainly understand throwing an exception.I'm not saying you have a bad idea/logic here, just that I'm not sure it's the best thing for the dict.
In a language like Haskell, one can use traits to specify how to merge values (Monoid) but without traits (and a way to choose which trait to use) I think some kind of magic merge is not great.
I claim the operations one should really want with dicts are not set operations but rather more relational ones, ie {inner,outer,left,right} joins on the keys followed by some mapping to decide how to merge values.