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 each() macro #158

Open
masak opened this issue Jul 10, 2016 · 8 comments
Open

Implement each() macro #158

masak opened this issue Jul 10, 2016 · 8 comments

Comments

@masak
Copy link
Owner

masak commented Jul 10, 2016

Without going into too much detail, something like:

say(each([1, 2, 3]));

would translate into the moral equivalent of

say(1);
say(2);
say(3);

(That's almost a full specification of each — something need also be said about the semantics of multiple each() in a statement. I will leave that Pandora's box unopened for whoever decides to implement this.)

Two details as I see it are necessary for this:

  • Watcher macros — though, as usual, we could start by making this a hardcoded mechanism in the language.
  • The ability to parse each without it being a declared function.
    • Actually, there are two ways, I guess. Either have a mechanism that allows us to mark some identifiers as OK even though they were not declared. This would be useful in general both for routine macros and watcher macros. I think I'm going to open up another issue about that.
    • ...Or we just have the each pragma tell its invoking parser to install a symbol each into itself. The advantage to this would be that we could detect and die on redeclarations of each (e.g. someone imports the pragma as well as declares their own sub each).
@masak
Copy link
Owner Author

masak commented Aug 19, 2016

Just a quick thought: maybe a watcher macro is too heavy-handed here. It seems that what's really going on is that an each() call has some kind of relation to its surrounding statement. What you'd want is a kind of macro that says "match on each() in a statement". The macro would get not just the each() Q node (which is not enough), but also the surrounding statement node.

It's a little bit like jQuery's .on() method allows you to attach an event handler to something higher up in the hierarchy:

$( "table" ).on( "click", "tr", function() {
  console.log( $( this ).text() );
});

jQuery calls this "delegated events", and it gives you access to both the table and the tr as needed.

@masak masak changed the title Implement each() using watcher macros Implement each() macro May 26, 2017
@masak
Copy link
Owner Author

masak commented May 26, 2017

I came back in my thoughts to the "delegated events" idea the other day. I believe it carries its own weight.

I tried to write this up realistically:

macro each(array_ast: Q::Term::Array) {     # only consider literal arrays
    inside Q::Statement::Expr -> statement_ast {
        my new_statement_asts = array_ast.elements.map(sub (expr_ast) {
            return statement_ast.clone().find(...).replace(expr_ast);
        });
        statement_ast.replace(new_statement_asts);
    }
}

Some immediate comments/thoughts:

  • It's increasingly clear that we need to think about the "AST manipulation API". Above, it involves clone, find, and replace.
  • The point of the inside statement (which should be a macro, naturally) is twofold: (a) to go and grab the innermost surrounding AST node of a certain type, and to (b) complain at compile time with a nice explanatory error if none is found — in this case if each was somehow used outside of an expression statement (for example in a trait).
  • The thing I did not know offhand how to write (the ... above) is "find me, the each on which this macro was invoked, in the newly-cloned AST. Feels like there should be a solution there; one does not come to mind right now, is all.

@masak
Copy link
Owner Author

masak commented May 26, 2017

Hm, maybe a cloneReplace would capture intent better:

macro each(array_ast: Q::Term::Array) {     # only consider literal arrays
    my each_ast = ...;
    inside Q::Statement::Expr -> statement_ast {
        statement_ast.replace(array_ast.elements.map(sub (expr_ast) {
            return statement_ast.cloneReplace(each_ast, expr_ast);
        }));
    }
}

Now the ... indicates that I don't quite know how to access the AST that macro each was called on. But that feels like something that quite reasonably ought to be in the context of the macro somehow. So overall, this is a simpler, neater solution.

@masak
Copy link
Owner Author

masak commented May 26, 2017

Just noting in passing that the inside statement macro (which probably deserves to be tracked by its own issue) also requires whatever mechanism finds the Qnode that the macro was called on.

@masak
Copy link
Owner Author

masak commented Jul 11, 2017

Coming back to the suggestion of inside a bit later, I'm peeved that the inside/outside relation is reversed; that the macro that's inside its context is textually declared outside that context's inside declaration.

I'd rather have something that respects the inside/outside relation:

inside Q::Statement::Expr -> statement_ast {
    macro each(array_ast: Q::Term::Array) {     # only consider literal arrays
        my each_ast = ...;
        statement_ast.replace(array_ast.elements.map(sub (expr_ast) {
            return statement_ast.cloneReplace(each_ast, expr_ast);
        }));
    }
}

One immediate benefit of this is that we can now group several macro declarations (and perhaps other declarations) inside the same inside.

First I felt it's unfortunate that macro each is now not globally scoped to the whole program — but this actually corresponds to that macro being scoped inside expression statements. It highlights rather than muddles the fact that each should not be called outside. And it frees up each to be re-used in other contexts.

@masak
Copy link
Owner Author

masak commented Jul 26, 2017

The nice/consistent thing about the inside block is that it provides its own lexical environment inside which things can be freely shared.

The not-so-nice/not-so-consistent thing is that macros declared in the inside block are somehow visible outside of it. Which is a decidedly non-lexical notion. Anything non-lexical ought to be viewed with suspicion, mostly because lexical things tend to have extremely good long-term design fitness.

This is related to the idea of an export block about which I ramble in #53 (comment). Especially since (with the each macro example above) we might want to export the each macro through a module, essentially doing both inside and export at the same time.

@masak
Copy link
Owner Author

masak commented Mar 13, 2019

I just found this forgotten gist, and wanted to link it here for archival purposes.

It's a great relief #349 actually provides a concrete answer to "The only question is how to inject that macro at the statement level."

@masak
Copy link
Owner Author

masak commented Mar 26, 2019

Re-reading this issue, it's interesting (and fantastic) how almost all the discussion in it has been superseded by #349.

Two questions remain, though:

  • Should each interact in any way with infix:<=> precedence? (Like junctions; see Contextual macros #349 for discussion.)
  • Should each work only with literal arrays, arrays whose contents are known at compile time (i.e. @constants), or any runtime values? (This question invokes the ghost of A "do it now or do it later" macro #304.)

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

1 participant