
My original dict unpacking proposal was very short and lacked a motivating usage. Toy examples made my proposal look unnecessarily verbose and suggested obvious alternatives with easy current syntax. Nested/recursive unpacking is much more troublesome, especially when combined with name-binding. I wrote an example to compare my proposal with current syntax. Example usage. https://github.com/selik/destructure/blob/master/examples/fips.py Implementation. https://github.com/selik/destructure/blob/master/destructure.py The design of my module I'm least happy with is the name-binding. I extended a SimpleNamespace to create an Erlang-style distinction between bound and unbound names. Though the API is a bit awkward, now that the module is built, I'm less enthusiastic about introducing new syntax. Funny how that works. I haven't yet decided how to add post-binding guards to the cases.

On 28 May 2016 at 08:22, Michael Selik <michael.selik@gmail.com> wrote:
Interesting! Thanks for taking the time to make a real-world use case. I haven't looked at the module yet, just the example, but the code does look pretty clean and readable. The example is certainly complex enough that I'd probably end up with pretty messy and fragile code if I just tried to put something together with pure Python code. And yes, it's interesting how finding a good API for a module can make the need for a dedicated syntax less pressing. But working out that good API can be really hard (I don't think I'd ever have thought of doing it the way you did). Paul

I'm sure y'all are too busy with PyCon to be checking email. But if you're curious, I added attribute unpacking and post-binding guards ( https://github.com/selik/destructure). The object unpacking works as most folks have suggested, via kwargs. The guards unfortunately need to be functions that take no parameters. match(schema=Foo(a=1, b=bind.x), data=Foo(a=1, b=2), lambda : bind.x > 0) On Sat, May 28, 2016 at 6:26 AM Paul Moore <p.f.moore@gmail.com> wrote:

I see a little problem if we want to match more cases: from destructure import Binding, Switch o = Binding() schema1 = [2, o.x, 3] schema2 = [2, 4, o.x] s = Switch([2, 4, 3]) if s.case(schema1): print(o.x) if s.case(schema2): print(o.x) 4 --------------------------------------------------------------------------- BindError Traceback (most recent call last) [...] BindError: name 'x' has already been bound to 3 I prefer to enable multiple matches. But if you really like to forbid it then you would probably like to change next line -> raise BindError(fmt.format(name=name, value=value)) to line -> raise BindError(fmt.format(name=name, value=getattr(self, name))) Anyway I really like your proof of concept! :) To improve it maybe you could distinguish cases in readme.rst. For example: schema1 = [1, o.x, 3] schema2 = [2, 4, o.x] s = Switch([2, 4, 6]) if s.case(schema1): print(o.x) elif s.case(schema2): print(o.x) else: print('otherwise') 6 2016-05-30 8:52 GMT+02:00, Michael Selik <michael.selik@gmail.com>:

On Mon, May 30, 2016 at 4:48 PM Pavol Lisy <pavol.lisy@gmail.com> wrote:
I prefer to enable multiple matches.
Ok. Preventing rebinding was inspired by Erlang, but I can see why you might want to match multiple cases. I'll move that feature to a FreezeBinding class; I figure it'll be helpful in multithreading.
Fixed.
Anyway I really like your proof of concept! :)
Thanks for reading.

On Wed, Jun 1, 2016 at 9:37 AM Sven R. Kunze <srkunze@mail.de> wrote:
more flexible than a monolithic switch-case
That wasn't my initial intention, but I guess that's a side-effect of needing to break it down into pieces viable in current syntax. Thanks a lot, Michael.
You're welcome. I hope the effort informs the discussion of a special matching syntax. Some things that really stood out as tough to implement or not possible. 1. Avoid NameErrors when specifying identifiers to bind in a schema. I see two options to implement this in current syntax. I chose to require a binding object and all identifiers must be attributes of that binding object. Another option would be postponing the lookup of the identifiers by requiring the schema to be defined in a function, then doing some magic. 2. Matching an object and unpacking some of its attributes. If the schema is a fully-specified object, we can rely on its __eq__ to do the match. If the schema specifies only the type and does not unpack attributes, that's even easier. The tough part is a schema that has a half-specified object where where its __eq__ cannot be relied on. If all Unbounds are equal to everything, that would help a half-specified schema compare well, but it breaks all sorts of other code, such as ``list.index``. I chose to compare all public, non-Unbound attributes, but that may not have been the implementation of its __eq__. Alternatively, one could trace __eq__ execution and revise Unbound comparisons, but that's beyond my wizarding abilities. 3. Unpacking the attributes of an object that aggressively type-checks input. Given my solution of a Binding object and unpacking into its attributes, one cannot, for example, unpack the first argument of a ``range`` as it raises a TypeError: ``bind=Binding(); schema = range(bind.x)``. I'm considering removing object-unpacking from the module. To match and unpack an object, the schema could be specified as a mapping or sequence and the user would transform the object into a mapping (like ``obj.__dict__``) before doing the match.

On 01.06.2016 21:04, Michael Selik wrote:
Which I for one doesn't regard as a drawback.
This all matches with my expectations I expressed on the other thread. Maybe, it's another indication that one should not split attributes from an object at all but rather use the object as a whole. This said, lists/tuples and dictionaries are more like a loose collection of separate objects (allowing unpacking/matching) whereas an object represents more an atomic entity. Sven

On 28 May 2016 at 08:22, Michael Selik <michael.selik@gmail.com> wrote:
Interesting! Thanks for taking the time to make a real-world use case. I haven't looked at the module yet, just the example, but the code does look pretty clean and readable. The example is certainly complex enough that I'd probably end up with pretty messy and fragile code if I just tried to put something together with pure Python code. And yes, it's interesting how finding a good API for a module can make the need for a dedicated syntax less pressing. But working out that good API can be really hard (I don't think I'd ever have thought of doing it the way you did). Paul

I'm sure y'all are too busy with PyCon to be checking email. But if you're curious, I added attribute unpacking and post-binding guards ( https://github.com/selik/destructure). The object unpacking works as most folks have suggested, via kwargs. The guards unfortunately need to be functions that take no parameters. match(schema=Foo(a=1, b=bind.x), data=Foo(a=1, b=2), lambda : bind.x > 0) On Sat, May 28, 2016 at 6:26 AM Paul Moore <p.f.moore@gmail.com> wrote:

I see a little problem if we want to match more cases: from destructure import Binding, Switch o = Binding() schema1 = [2, o.x, 3] schema2 = [2, 4, o.x] s = Switch([2, 4, 3]) if s.case(schema1): print(o.x) if s.case(schema2): print(o.x) 4 --------------------------------------------------------------------------- BindError Traceback (most recent call last) [...] BindError: name 'x' has already been bound to 3 I prefer to enable multiple matches. But if you really like to forbid it then you would probably like to change next line -> raise BindError(fmt.format(name=name, value=value)) to line -> raise BindError(fmt.format(name=name, value=getattr(self, name))) Anyway I really like your proof of concept! :) To improve it maybe you could distinguish cases in readme.rst. For example: schema1 = [1, o.x, 3] schema2 = [2, 4, o.x] s = Switch([2, 4, 6]) if s.case(schema1): print(o.x) elif s.case(schema2): print(o.x) else: print('otherwise') 6 2016-05-30 8:52 GMT+02:00, Michael Selik <michael.selik@gmail.com>:

On Mon, May 30, 2016 at 4:48 PM Pavol Lisy <pavol.lisy@gmail.com> wrote:
I prefer to enable multiple matches.
Ok. Preventing rebinding was inspired by Erlang, but I can see why you might want to match multiple cases. I'll move that feature to a FreezeBinding class; I figure it'll be helpful in multithreading.
Fixed.
Anyway I really like your proof of concept! :)
Thanks for reading.

On Wed, Jun 1, 2016 at 9:37 AM Sven R. Kunze <srkunze@mail.de> wrote:
more flexible than a monolithic switch-case
That wasn't my initial intention, but I guess that's a side-effect of needing to break it down into pieces viable in current syntax. Thanks a lot, Michael.
You're welcome. I hope the effort informs the discussion of a special matching syntax. Some things that really stood out as tough to implement or not possible. 1. Avoid NameErrors when specifying identifiers to bind in a schema. I see two options to implement this in current syntax. I chose to require a binding object and all identifiers must be attributes of that binding object. Another option would be postponing the lookup of the identifiers by requiring the schema to be defined in a function, then doing some magic. 2. Matching an object and unpacking some of its attributes. If the schema is a fully-specified object, we can rely on its __eq__ to do the match. If the schema specifies only the type and does not unpack attributes, that's even easier. The tough part is a schema that has a half-specified object where where its __eq__ cannot be relied on. If all Unbounds are equal to everything, that would help a half-specified schema compare well, but it breaks all sorts of other code, such as ``list.index``. I chose to compare all public, non-Unbound attributes, but that may not have been the implementation of its __eq__. Alternatively, one could trace __eq__ execution and revise Unbound comparisons, but that's beyond my wizarding abilities. 3. Unpacking the attributes of an object that aggressively type-checks input. Given my solution of a Binding object and unpacking into its attributes, one cannot, for example, unpack the first argument of a ``range`` as it raises a TypeError: ``bind=Binding(); schema = range(bind.x)``. I'm considering removing object-unpacking from the module. To match and unpack an object, the schema could be specified as a mapping or sequence and the user would transform the object into a mapping (like ``obj.__dict__``) before doing the match.

On 01.06.2016 21:04, Michael Selik wrote:
Which I for one doesn't regard as a drawback.
This all matches with my expectations I expressed on the other thread. Maybe, it's another indication that one should not split attributes from an object at all but rather use the object as a whole. This said, lists/tuples and dictionaries are more like a loose collection of separate objects (allowing unpacking/matching) whereas an object represents more an atomic entity. Sven
participants (4)
-
Michael Selik
-
Paul Moore
-
Pavol Lisy
-
Sven R. Kunze