alma
alma copied to clipboard
Implement Smalltalk-style method chaining syntax
Courtesy of @arnsholt. Slightly made-up but complete example:
my employee = {
name: "Frank",
age: 44,
role: "Senior Janitor",
printName() {
say(employee.name);
},
printAge() {
say(employee.age);
},
printRole() {
say(employee.role);
}
};
employee
..printName()
..printAge()
..printRole()
;
That is, even though those printMumble
methods don't explicitly return the employee
object (or some this
equivalent), the ..
call syntax allows them to be used as if they did.
Here's a possible implementation:
macro postfix:<..>(object_ast, identifier_ast, parameterlist_ast)
is parsed(/ ".." <identifier> "(" <parameterlist> ")" / {
return quasi {
{{{object_ast}}}.{{{Q::Identifier @ identifier_ast}}({{{Q::ParameterList @ parameterlist_ast}}});
{{{object_ast}}};
};
}
Looks good to me. Only quibble I have is on the macro implementation: if object_ast is something that performs a destructive operation (say, getting the next item off a queue or something), wouldn't the macro end up performing many of them, rather than just one?
Oh, another thing. In ST invocant m1; m2; m3
has the return value of m3
, not the invocant itself which is what we get with this macro.
Only quibble I have is on the macro implementation: if object_ast is something that performs a destructive operation (say, getting the next item off a queue or something), wouldn't the macro end up performing many of them, rather than just one?
Yes, you're right. More generally, with quasis in macros, whenever one is using the same value multiple times, one always wants to capture, with no exception that I've found so far. (Later edit: This is now discussed in #234 and #479.)
Ok, here's a new implementation that does that:
return quasi {
my object = {{{object_ast}}};
object.{{{Q::Identifier @ identifier_ast}}({{{Q::ParameterList @ parameterlist_ast}}});
object;
};
Oh, another thing. In ST
invocant m1; m2; m3
has the return value ofm3
, not the invocant itself which is what we get with this macro.
Hm, you're right. The last method shouldn't return the invocant, because that's clearly useless, just like it's useful in earlier places in the chain.
Let's see... postfixes nest like this: ((employee..printName())..printAge())..printRole()
. The corresponding macros get called innermost-first, that is, from left to right. Which means that what a postfix:<..>
macro should do is not codegen its own method call, but rather tweak the previous method call, if any, to return the invocant.
Under that scheme, ..printRole()
which is called last ends up tweaking ..printAge()
, but nothing ends up tweaking ..printRole()
so it ends up returning what it usually does (None
).
Yes, this scheme is more ambitious, but I like it better, I think.
Hm, you're right. The last method shouldn't return the invocant, because that's clearly useless, just like it's useful in earlier places in the chain.
Yup. In ST, if that's actually what you want, you can just call the identity method, i.e. invocant m1; m2; yourself
Yeah, for maximum manipulexity you want to be able to choose between the invocant and the returned value. In ST it's solved by the chaining operator returning the last return value and having a method that just returns the invocant (called yourself
actually, not self
. self
is the reserved word containing the invocant of a method).
Smalltalk's syntax is different than the one I propose in OP. It uses semicolons and periods.
I think I might have gotten the ..
syntax from LiveScript cascades. Who knows how long ago I read that. :smile: