Skip to content

Instantly share code, notes, and snippets.

/plz2review.md Secret

Created October 5, 2015 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/8efdab238ead35b1d4fa to your computer and use it in GitHub Desktop.
Save anonymous/8efdab238ead35b1d4fa to your computer and use it in GitHub Desktop.
Calling/coercion cleanup

Calling/coercion cleanup

Calling and coercion kinda share syntax and the boundaries aren't as clear as would be desirable. Also we didn't quite settle on coercion stuff yet, and there are some things we need to decide whether to keep.

Custom invocation

Done by writing a CALL-ME method, which gets the args passed (not the Capture, but directly the args). All previous forms are gone (so no more method postcircumfix:<( )>).

Shortcuts

We currently have:

method &.(|args) { } # mapped to postcircumfix:<( )>
method @.(|args) { } # mapped to postcircumfix:<[ ]>
method %.(|args) { } # mapped to postcircumfix:<{ }>

These are useless. We should kill them off, or change them to:

method &.(|args) { } # mapped to CALL-ME
method @.($idx) { } # mapped to AT-POS
method %.($key) { } # mapped to AT-KEY

They may not be worth keeping. The last two are a tad odd as they special-case just one of the things you should implement if making a custom indexable type (you really want to do EXISTS-POS as well as AT-POS, for example).

Coercion type literals

Anything that matches:

<typename> '(' <.ws> ')'
<typename> '(' <typename> ')'

Is a coercion type literal. Example: Int(Str). Open question: do we need a (non-MOP) way to make a coercion type out of two other types? (You can always do it via. the MOP if needed). This means that MyType(SomeType) will never call a CALL-ME in MyType.

Coercions

Coercions are a bit of a mess at the moment. They're very confused with invocation - and the way to implement a coercion seems to be "implement CALL-ME", which isn't really intuitive. We also have no syntactic distinction for what is/isn't a coercion. Here's a proposal.

Anything that matches:

<typename> '(' <EXPR> ')'

Will be taken as a coercion. Note that we can't quite implement it like this, since in the grammar this is spread over term:sym and term:sym. So both of those need to look if the thing they parsed is a type. Assuming the parsed type is $type, $typename contains the compile time evaluated result of $type.^name, and the parsed expression is in $expr, a coercion will compile to the following:

nqp::istype($expr, $type)
    ?? $expr                         # Already matching type
    !! nqp::can($expr, $typename)
           ?? $expr."$typename"()    # Coercion method
           !! $type.COERCE($expr)    # Call .COERCE

This is the code that a coercion in a signature should also result in. Notice that in the no coercion case a dynamic optimizer (like MoarVM spesh) can maybe toss the whole lot and just reduce it to $expr, and in the csae we need a coercion it can often resolve the nqp::can at specialization time too.

Note that $foo(42) is always unambiguously a call (and goes to CALL-ME). To force a call on a type object, do MyType.(42) (the extra dot forces it to be invocation, since it doesn't syntactically get considered a coercion).

The default COERCE method dies with an error saying it doesn't know how to coerce from one type to the other, which is a rather better failure mode than "no such method X" or "no CALL-ME" and so forth.

Open question: how do we provide a nice way to get access to the coercion logic (check type, then check for method, then call COERCE) when you don't want to write the type literally into the code? Preferably as a compiler-recognized form so we can do the same nice code-gen and give dynamic optimizers a chance at doing something sane in the non-megamorphic case.

@smls
Copy link

smls commented Oct 31, 2015

One thing this gist does not cover, are the method forms for coercions.

This too is a bit of a mess currently in Rakudo, with method and "sub" forms being synonyms for each other for some built-in types, but not others:

say (2.1).Int;      #-> 2
say Int(2.1);       #-> 2

say "test.txt".IO   #-> "/home/smls/test.txt".IO
say IO("test.txt")  #!  Cannot invoke this object

I think TimToady once recommended on IRC that one of those forms should automatically fall back to the other, but I don't remember which, and can't seem to find the quote on the quick.

In any case, it would be nice to have consistency there (for both built-in types and user types).

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