Summary: dict.update(self, other) corresponds to 'merge-right'. Perhaps add
dict.gapfill(self, other), which corresponds to 'merge-left'. This post
defines dict.gapfill, and discusses update and gapfill in the context of
PEP 584.

PEP 584 suggests adding a merge operator (to be denoted by '|' or '+') to
dictionaries, and a corresponding augmented assignment operator (to be
denoted by '|=' or '+='). The main purpose of this post is to remind
ourselves that there are both merge-left and merge-right operators, and to
begin to discuss the consequences.

Perhaps Off Topic: There is already a road sign for 'merge left'. See the
following URL for a discussion of usual 'W4-2' sign for this operation, and
how it can cause confusion. And how a "small but critical" change to the
sign reduced confusion, in a backwards compatible way.

https://99percentinvisible.org/article/lane-ends-merge-left-redesigning-w4-2-road-sign-end-confusion/

By the way, that URL writes: "The solution is elegant, simple and additive
— it requires no fundamental reformatting, building instead on the existing
sign. It is thus also recognizable to those familiar with its predecessor
and visually similar to older signs that can still be found on the road."
To me, it seems that the author has goals similar to the Python design
goals.

See also http://www.trafficsign.us/pdf/warn/w4-2mod.pdf, which uses 'Left
Lane Ends' instead of 'Merge Right' (and similarly 'Right Lane Ends' for
'Merge Left').

Now Back On Topic. The dict.update(self, other) method gives priority to
other. (In other words, it's 'merge-right'.) In March this year, it led me
to realise that perhaps dict would benefit from there being a 'merge-left'
operator, which gives priority to the left.

Here's an example of how merge-left (which I call dict.gapfill) could be
coded. (I've done it in a way that emphasises similarities and differences
between dict.update and dict.gapfill.)

class mydict(dict):

    def update(self, other):
        for key, value in other.items():
            if True:
                self[key] = value

    def gapfill(self, other):
        for key, value in other.items():
            if key not in self:
                self.key = value

    def copy(self):
        return mydict(super().copy())

Here's some simple examples of the semantics.

>>> d1 = mydict(); d1[1] = 'int one'
>>> d2 = mydict(); d2[1.0] = 'float one'

>>> c1 = d1.copy(); c1.update(d2)
>>> c2 = d1.copy(); c2.gapfill(d2)

>>> c1
{1: 'float one'}
>>> c2
{1: 'int one'}

For comparison with sets (similar to dict keys), we also have

>>> {1}  |  {1.0}
{1}
>>> {1.0}  |  {1}
{1.0}

Now for some consequences.

First, it's my opinion that dict.gapfill(self, other) will be useful when
we're given say some command line values, and we wish to augment this with
default values. Second, it's my opinion that dict.gapfill(self, other) is
in some cases a useful alternative for a dict merge operator (whether it
uses '|' or '+' as its symbol). For evidence see
https://github.com/jpadilla/pyjwt/blob/master/jwt/utils.py#L71-L81

Third, for sets the '|' operator is a merge-left operator. In other words
it gives priority to the set-element (or key) in the first set, rather than
the second. (This happens only when the two keys so-to-say are the same
value represented by different types. In our case the types are (int,
float).)

Fourth, as the set '|' operator is merge-left, and Python's
>>> value = A or B or C
gives priority to the first (left-most) true value, confusion might result
if the dict '|' operator were to be merge-right.

Regarding the fourth point, it is possible that purity and consistency
points to  the dict '|' operator being merge-left, while usefulness and
habit points to the dict '|' operator being merge-right.

Fifth and finally. In our context, it is my opinion that merge-left and
merge-right don't clearly communicate the differing semantics. However, in
my opinion dict.update(self, other) and dict.gapfill(self, other) do
clearly communicate their semantics.

Here's a link to my post to this list in March this year.
https://mail.python.org/archives/list/python-ideas@python.org/message/GRFYX3KG7BNUU7CMLZTRTH32MMI2V4TR/

with best regards

Jonathan
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/BA34PUDUJR23HI7XUCCPAFI3TPTXH7W5/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to