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 conditional/ternary/trinary operator (?? !!) as an 'is parsed' macro #163

Open
masak opened this issue Aug 3, 2016 · 7 comments

Comments

@masak
Copy link
Owner

masak commented Aug 3, 2016

007 is hanging its head in shame over its lack of a ternary operator. The only way to restore its name to glory is to implement it in style, namely as an is parsed macro.

Here's my best take at how it'd look:

macro infix:<?? !!>(condition: Q::Expr, then_part: Q::Expr, else_part: Q::Expr)
    is parsed(/"??" <EXPR> "!!"/) is tighter(infix:<=>) {

    # The implementation is not that interesting; including it for completeness
    return quasi {
        my result;
        if {{{condition}}} {
            result = {{{then_part}}};
        }
        else {
            result = {{{else_part}}};
        }
        result;
    };
}

Some thoughts:

  • I'm not at all sure I want /.../ for regexes in 007. But it's the lazy/obvious default.
  • I believe the regex itself is correct... modulo whitespace handling, which I'm never sure about before the tests pass. (And typically not after they do, either.)
  • There has to be a way to bind the <EXPR> things to the parameters in the macro. I'm not sure what the mechanism should be. For now, I've gone with the simplest possible, saying they bind in order, disregarding names and stuff. This is probably too simplistic; we might want to do something closer to the kinds of binding $/ does.
  • In the same vein, note that we don't bind condition and else_part at all; condition has already been parsed at this point, and gets sent into the macro as a courtesy because it's an infix. else_part will simply be parsed as the rhs of the infix.
  • Note that, unlike in Perl 6, where the resulting bindings from the regex would be Match objects, in 007 they are Qnodes.
  • Missing from the implementation is some checking that = isn't included between the ?? and the !!. In Rakudo, this is done by calling <EXPR> at a certain precedence level (<EXPR('i=')>), and then checking if there's an infix. We might want to do it like that; calling into <EXPR> at a certain precedence level is probably something 007 wants to do sooner or later anyway.
  • I was thinking before I saw Rakudo's implementation that we might want to just call <EXPR> and then post-analyze it for =. But that's probably not going to fly, since parentheses don't have a Q representation. I guess this use case is a (weak) argument for parentheses having a Q. But then we'd probably also need a circumfix operator category...
  • Similarly, a = after the !! should not be parsed as part of the rhs. This falls out of normal precedence, though.

Seeing the above, I'm pleasantly surprised at how little additional machinery would be needed for this. Basically we'll need regex support, which we can start playing with under a feature flag today if we want. The rest we can hard-code to various degrees in the short run.

I had no idea whether we currently even allow the definition of an infix routine with spaces in it. Turns out not only do we allow it, but we parse it as an operator, too!

$ bin/007 -e='macro infix:<?? !!>(lhs, rhs) { return quasi { 5 } }; say(2 ?? !! 3)'
5

I'm not sure how I feel about that. It will need some kind of whitespace-related ruling at some point.

@vendethiel
Copy link
Collaborator

I'm very interested to know if you have any idea how is parsed/tighter-looser will interact. Because I can't think abouit at all

@masak
Copy link
Owner Author

masak commented Aug 3, 2016

I think the simplest way to phrase it is "they don't interact".

For example, the fact that we make an operator infix carries with it certain preconditions and postconditions for the parser. We'll only ever start parsing it in infix position, something like:

x = a ?? b !! c;    # nice

And never in term position, like:

y = ?? b !! c;    # bzzt -- won't happen

On the post-conditions side — again because infix — the custom op guarantees that after the is parsed regex is done parsing, it should expect a term:

a ?? b !! c;    # this
a ?? b !! = z;  # bzzt -- never this

When it comes to handling precedence (and associativity), the special parsing is a black box. It doesn't matter what happened inside of the is parsed, because from the point of view of expr parsing, the whole matched bit of source gets treated like a very wide infix operator without any particular internal structure. This is similar to how in good OO a supplier and a consumer of an API can collaborate without either knowing specifics of the other.

I'm confident that this will work out pretty well, because that's how it already works in Rakudo's src/Perl6/Grammar.nqp. (Though there's no is parsed mechanism yet to expose such parsing to a custom op author.)

Hope that answers it! I remember I/we were struggling with these bits in #18. That issue ended up being rejected with this parting message:

I think we can close this one for now; I see it being handled in other ways, mostly with a goodenuf is parsed mechanism.

Which is what is happening in this issue. 😄

@vendethiel
Copy link
Collaborator

Thanks for the clarification!

@masak
Copy link
Owner Author

masak commented Aug 8, 2016

Just a quick addition:

    is qtype(Q::Infix::Ternary)

Which will have the consequences that when someone later inspects the Qtree of a program with a ?? !! operator in it, they will see not the tree fragment from the expanded macro, but a Q::Infix::Ternary node.

This is important because to the extent we consider some built-in operators as having been defined "in code" (at least in theory), then they'd also need this mechanism.

But it's just as important for some custom things who want to look built-in to have this mechanism.

Presumably there'd be some way to ask the Q::Infix::Ternary for its underlying expanded Qtree fragment. At least the runtime itself would need to do that in order to know how to interpret it.

@masak masak changed the title Implement ternary operator (?? !!) as an 'is parsed' macro Implement ternary/trinary operator (?? !!) as an 'is parsed' macro Jul 26, 2017
@masak masak changed the title Implement ternary/trinary operator (?? !!) as an 'is parsed' macro Implement conditional/ternary/trinary operator (?? !!) as an 'is parsed' macro Jul 26, 2017
@masak
Copy link
Owner Author

masak commented Jul 26, 2017

In the case of a missing !!, the implementation of ?? !! must be able to emit a parser error that's just as good as what the compiler had emitted itself, had the operator been built-in:

# dream scenario:
$ bin/007 -e='import ternary; say (2 ?? 5);'
Expected "!!" but found ")"
    on line 1 of -e, near "2 ?? 5⏏)"

@masak
Copy link
Owner Author

masak commented Feb 10, 2019

I had two scattered thoughts today:

  1. The final implementation of ?? !! should ideally be able to propagate lvalues. That is, (a ?? b !! c) = 42 should be able to assign to b or c as appropriate. (This is not urgent. It's also a policy I'm fine with reversing, since this most certainly would not be something up with which Python would put.)

  2. This is not a well-formed thought yet, but it feels to me like the ?? !! macro has two "aspects" or "entry points": the first one is the syntactical one, the one that uses is parsed and uses the syntax a ?? b !! c. The second one is everything except the syntax; as a normal call, it'd look something like cond(a, b, c). (It'd still need to be a macro, because b and c are thunkish.)

The implementation in the OP has exactly the three parameters necessary for that to work; i.e. it'd simply be infix:<?? !!>(a, b, c). I like that that's an option. Unfortunately,

  • @vendethiel++ has convinced me that is parsed macros shouldn't magically map their captures onto positionals like that, but instead do things through a match parameter. Does this suggest that the is parsed macro and the callable infix:<?? !!> macro are two separate things? (As far as I can see, the former could still delegate to the latter, so that in itself isn't a problem.)

  • infix:<?? !!> actually breaks the Perl 6 naming convention that says infixes are meant to be one word. (This should be a separate issue; the closest we've come to tackling it was a drive-by mention in Seriously consider having an infix:<in> operator #244.) The space is "reserved" for circumfixes and postcircumfixes, both which have a start and a stop. Well, the conditional operator has that too, but only sort of unofficially. I asked on #perl6, but the answer was mostly confusion. Maybe infix:<??!!> is a reasonable compromise, although it doesn't feel ideal.

@masak
Copy link
Owner Author

masak commented Jul 24, 2023

  1. The final implementation of ?? !! should ideally be able to propagate lvalues. That is, (a ?? b !! c) = 42 should be able to assign to b or c as appropriate. (This is not urgent. It's also a policy I'm fine with reversing, since this most certainly would not be something up with which Python would put.)

Although Future Me is not 100% convinced this is a good idea, when I saw the suggestion here to support a kind of "reference transform", I thought about this quasi-use-case of making ?? !! propagate lvalues.

I was going to say that this maybe requires some degree of static typing... but maybe not. What it does require is a kind of "lvalue-ready invocation" (as Bel does) of the function/operator, which can be done at the last minute by the evaluator. Of course, in that case, one also gets a runtime error if the function returns something too rvalue-ish; this error in turn could be shifted left to compile time by a sufficiently static analysis — but it's not a requirement.

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

2 participants