Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Smalltalk-style method chaining syntax #221

Open
masak opened this issue Feb 2, 2017 · 7 comments
Open

Implement Smalltalk-style method chaining syntax #221

masak opened this issue Feb 2, 2017 · 7 comments

Comments

@masak
Copy link
Owner

masak commented Feb 2, 2017

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}}};
    };
}
@arnsholt
Copy link

arnsholt commented Feb 2, 2017

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?

@arnsholt
Copy link

arnsholt commented Feb 2, 2017

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.

@masak
Copy link
Owner Author

masak commented Feb 2, 2017

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;
};

@masak
Copy link
Owner Author

masak commented Feb 2, 2017

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.

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.

@vendethiel
Copy link
Collaborator

vendethiel commented Feb 2, 2017

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

@arnsholt
Copy link

arnsholt commented Feb 2, 2017

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).

@masak
Copy link
Owner Author

masak commented May 27, 2017

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. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants