masak / alma

ALgoloid with MAcros -- a language with Algol-family syntax where macros take center stage
Artistic License 2.0
137 stars 14 forks source link

Variable and parameter destructuring #306

Open masak opened 6 years ago

masak commented 6 years ago

Allowing destructuring in variable declarations and routine parameters might feel like two distinct tasks, but I'd say the would largely use the same underlying logic, so...

Let's make this really concrete. Something like

my { foo: bar, baz: [quux, aloha] } = someDict;

would compile down to something like

my bar = someDict["foo"];
my _internal_gensym_483765 = someDict["baz"];
my quux = _internal_gensym_483765[0];
my aloha = _internal_gensym_483765[1];

Specifically,

I'm undecided on whether structures having fewer keys/elements ought to fail hard (with an exception) or soft (with None, possibly recursively). Leaning towards the former, because if we end up using destructuring also in case statements, the destructuring needs to somehow signal failure up to the case statement, and an exception would be a good way to do that.

To me, the absolute measure of success of this as a macro would be if it interoperated well with #199. I think what we're looking at is a "binding instance" abstraction that's perhaps a bit ill-exposed right now in the language. (The closest we have is Q::Declaration, but even that's not quite it, since it's also consumed by function statements and the like.)

masak commented 6 years ago

Oh, and I wanted to include in OP how this is another beautiful example of same old expressions, new evaluation semantics. There's a kind of "show, don't tell" going on where just building the structure of the object you want to destructure ends up being (sometimes) clearer and more descriptive than specifying paths for how to get down to the data.

In other words, we're re-using people's prior knowledge of (possibly nested) dictionary and array literals to show which values we're initializing our declared variables with. It "looks right", but the semantics has been all switched out.

Since the variables we introduce belong to the surrounding "parent language", we might even possibly consider this a slang — one that is confusingly similar to object literals in the parent language. (One unusual thing about this slang, if it is one, is that the "holes" taking us back to the parent language have no syntactic markers like {{{ }}} in quasiquotes or ${ } in interpolating strings. Instead they are marked "by position".)

Relatedly, it'd be really cool (but above and beyond this issue) if more things than arrays and dictionaries could participate in destructuring. Ideally it should be an open set of "container types" (sets, for example) that could register somehow against a destructuring protocol. Ah, dreams. We're not in want of ambition, that's for sure.

masak commented 6 years ago

I should probably mention tuples here (with the ( ) syntax) as something I'd want to pattern-match on.

Amusingly, I think

my (foo) = tuple;

would be unambiguous even without the usual (unesthetic) final (foo,) comma, because in this context parentheses do not indicate grouping, and so that syntax is not already claimed.

Unfortunately, it falls to the language designer to decide whether to accept the (foo) syntax out of kindness/dwimminess, or to forbid it in the name of consistency.

masak commented 6 years ago

If we implement #308, we could even think of allowing constructors in destructuring:

my Complex(re, im) = cpx;    # assigns re and im

But that'd also be a "stretch goal", not least because there'd have to be a clear path to seeing that the parameters sent into an object correspond in an invertible way to the properties assigned in the object. Maybe it can only work for properties that are auto-assigned from constructor parameters, or something.

masak commented 5 years ago

The thing that says that variable declarations and parameter declarations should preferably be equally powerful and sort of "the same" feature in a language seems to be referred to as Tennent's Correspondence Principle, surrounded by a miasma of controversy over exactly what it means and how far it extends.

masak commented 5 years ago

Right now I think the compiler checks at parse-time whether the thing on the left of an assignment is something we can assign to. We still like to do that statically, of course, but interestingly, arrays and dicts become things that we can assign to once one flips on destructuring. So maybe this should be a protocol of some sort, too, where you'd register arrays and dicts to be "honorary lvalues". In fact, maybe that protocol can be the whole destructuring mechanism? You just specify the macro that should take over when an array literal or a dict literal (respectively) is detected on the left side?

vendethiel commented 5 years ago

I'm not sure I get that last message.

This means

my a = [1, 2, 3];
a = [3, 2, 1];

should actually replace at indices? How do you replace a's value then? Would there be a syntax like a[*] = [3,2,1];?

masak commented 5 years ago

No, no. Not planning to mess with a normal variable assignment like a = [1, 2, 3]. Don't worry.

My whole point was that (a) the compiler can't be over-eager when disqualifying things it doesn't recognize as valid assignment targets (like array and dict literals with variables in them), (b) possibly the extension point you'd use from the destructuring-syntax module to register those as new allowed assignment targets, would also be the place where you'd specify (in a macro, most likely) what their semantics should be.