Algebraic Data Types - Iteration 2018-07-29
In this post:
- A stream of consciousness about changes to Structured Data inspired by real-world use.
- A roundup.
So, I tried to use Structured Data in Homunculus, and I was at first focused on redoing the input handling routines to be parsing some kind of data structure rather than a bunch of nested functions.
Needless to say, I'll be toning down the undertone of "obviously this is how it should be" when I revisit this for a post, because the direction I'm taking this is diverging radically from any kind of normal Python code.
Regardless, I have some feature requests I wrote for myself, that I'll be discussing for this post:
- Guards. A unary function whose result is interpreted as a boolean. They don't inherently bind anything, but they can be given a structure to match if they succeed. If they fail, the match fails. As a special case, there should be guards for truthiness and falsiness provided by the library.
- Binds. Top-level associations between an overall structure, a binding, and a value. If the overall match succeeds, the value is placed in the binding.
- Transformers. Pairs of an unary function and a structure. The function is applied to the value to match, and its result is matched by the structure.
Thinking about these, I'm coming to the conclusion that I want to rework some of the syntax/calling conventions.
Instead of overloading the matrix multiplication operator, I want to give patterns a __call__ implementation to allow for as-patterns.
Binds should be a method of patterns, where the first argument is the structure and the second is the value. Alternatively, a library function that puts the pattern and value together.
Guards should have a one-argument and two-argument form, different classes, where the one-argument version can convert to the two-argument version somehow.
Transformers could potentially have a one-argument version, but it shouldn't be valid for matches.
The first thing I want to change is to switch from __matmul__ to __call__. Let's see how that goes. I concluded that I want to use indexing, to avoid the hassle of having a bunch of parentheses. We'll see how that works out for me.
While I'm thinking about ergonomics, it'd be cool if there were some way to get the dict stuff out of DictPattern and AttrPattern.
Like if there a "base" and a "decorated" version of the patterns, where the "base" can just take arbitrary kwargs, but there's some way to get the "decorated" version, which is going to have non-default attributes. I went and did this a different way, probably simpler. And then I realized that I simplified DictPattern too much, reverted it, and stopped trying to have the classes have sort of the same interface.
I've decided that binds should be a specific kind of object, that take a single anonymous argument and arbitrary named arguments, so a single bind can bind many names.
I've got binds done, next to do (later!) would be guards, then transformers. I also have to be ready to redo what I currently have in Homunculus for the next release, which will be a minor version, because I went and redid some APIs on account of this isn't stable yet and I didn't like them.
I know that transformers have to be two-argument because there's no point in applying a function if you're not going to destructure the result. (Shhh. I want there to be no reason to do so.) I'm not sure how they should combine with other things, given that I sort-of care about the order that bindings happen in. Perhaps transformers should extend themselves via repeated calls, and those calls handle one-argument and two-argument differently. One-argument just takes a pattern, two-argument is function, then pattern. Then people can do the matches in whatever order they want.
Guards are one-argument and use indexing to add in structure for if they pass.
These are certainly some ideas, but I'm not feeling terribly sure of them, so I'm going to sleep on this.
For sure, though, this is an exciting time to follow Structured Data, in that my response to attempting to use this stuff once is to aggressively rewrite the constructors.
Okay, here's exactly where things ended up:
- As-patterns are now formed with indexing instead of matrix multiplication.
- AttrPattern now takes keyword arguments directly.
- New Bind class defines matches that are applied after an overall match succeeds.
Next time, I go over this last week of work.