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 melt(q) builtin #61

Closed
masak opened this issue Oct 27, 2015 · 6 comments
Closed

Implement melt(q) builtin #61

masak opened this issue Oct 27, 2015 · 6 comments
Assignees

Comments

@masak
Copy link
Owner

masak commented Oct 27, 2015

It's easy to go from runtime values to Qtrees: just throw something into a macro, or pass it into a quasi. It's less easy to go the other way: have Qtree, want runtime value.

Maybe think of it as an eval function, but rather than a string, the input is a Qtree (that is, already parsed or built synthetically). I don't want to call it eval — too much risk of confusion. So let's call it melt. The metaphor here sort of is that Qtrees are solid/frozen because they tend to hang around at compile time, whereas runtime values are liquid/runny because they, well.

Here, have an example:

my ast = Q::Infix::Addition {
    lhs: Q::Literal::Int { value: 2 },
    rhs: Q::Literal::Int { value: 40 }
};
say(melt(ast));     # 42

What do I see it being used for?

  • Evaluation of Q literals
  • Evaluation of expression trees
  • Constant folding
  • Some dynamic stuff that's hard to do with macros and quasis

Let me show an example of the latter (and what led me to want melt()):

sub call(name) {
    melt(Q::Identifier { name: Q::Literal::Str { value: name } })();
}

The function call allows you to pass in a string "foo", and the function foo() will be called.

Note that melt() decidedly is not a general-purpose evaluator — another reason not to call it eval. It returns values only. Do not expect it to do the right thing if you pass it Qtrees for blocks, unquotes, parameter lists, argument lists, statements, or traits. Anything that's a subtype of Q::Expr is fine though, including:

  • Literals
  • Operators
  • Identifiers
  • Quasis (!)

Similar to how splicing works in other places: if you melt a Qtree that comes from code (either a quasi or a macro argument), then it'll do lexical lookups from its original position in the code. If you melt a synthetic Qtree (that you built by hand, you craftsperson you!), then lookup will happen form the site of the melt() call. (That is, from melt's caller — in the above example, from inside call.)

You're free to use melt() in a macro or a BEGIN block, but do note that any identifiers you try to resolve will resolve very early. That is, if you're trying to melt an expression with a variable in it, chances are you'll be disappointed and get None. (Melting something inside a quasi block would work, but I doubt it has any advantages compared to the more powerful unquote.)

@vendethiel
Copy link
Collaborator

I propose "unwrap" as the name

@masak
Copy link
Owner Author

masak commented Oct 27, 2015

Oh, and I should mention that I had a freeze(e) in mind, too:

my literal = freeze(42);
say(literal);     # Q::Literal::Int 42

But what should be the exact semantics of freeze()? In particular, given freeze(my_str), should it give back

  • ...a literal Q::Literal::Str("this is the name of the song")
  • ....or an identifier Q::Identifier("my_str")

First I thought "well, clearly the second behavior is way more flexible, let's do that". And then "ooh! looks like freeze could be a macro, even!". And then I realized the implementation would be very easy:

macro freeze(e) {
    return quasi { e };    # look, no unquote!
}

So, yeah... freeze would be quasi-quoting, basically. Don't need that.

Maybe the former semantics (always evaluate fully, and then give back the literal or whatever) makes sense. But I haven't found a use case yet, to be honest. So I'm not proposing we add freeze().

@masak
Copy link
Owner Author

masak commented Oct 27, 2015

I propose "unwrap" as the name

Well, "unwrap" makes a lot of sense for literal Qtrees. Less so for identifiers or expression trees.

@masak masak self-assigned this Nov 28, 2015
@masak masak closed this as completed in ba6911a Nov 28, 2015
@masak
Copy link
Owner Author

masak commented Nov 28, 2015

After implementing this, I realized that since melt() allows the evaluation of function calls, it's morally equivalent to a full-featured eval() function.

This is not what I intended. I don't see any horrendous consequences of the language suddenly having eval(), but I have mixed feelings about introducing this unintentionally. The list of use cases was for more static/predictable things:

  • Evaluation of Q literals
  • Evaluation of expression trees
  • Constant folding
  • Some dynamic stuff that's hard to do with macros and quasis

Not sure what to do about this, which is why I'm not opening a new ticket about it.

Blacklisting function calls altogether feels unfair to operators, which are effectively function calls with sugar on top. What I think we do want to do is whitelist a bunch of operators and functions among the built-ins. Basically anything that constant folding would be able to relate to. This is a thought for the future — let's hope in the meantime users don't get heavily dependent on the unintended semantics.

@vendethiel
Copy link
Collaborator

maybe restrict its use to BEGIN time?

@masak
Copy link
Owner Author

masak commented Nov 28, 2015

maybe restrict its use to BEGIN time?

  1. That would eliminate uses I can't predict, some of which may be both legitimate and interesting

  2. It fails to address the problem of side-effects inside called functions being allowed. Vide:

    $ perl6 bin/007 -e='sub f() { say("oops") }; BEGIN { f() }'
    oops
    

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

No branches or pull requests

2 participants