problem-solving icon indicating copy to clipboard operation
problem-solving copied to clipboard

Semantics of coercion type on an "rw" parameter

Open jnthn opened this issue 5 years ago • 8 comments

Currently, if writing:

sub foo(Num() $n is rw) { $n++ }

Then we can call it successfully like this:

my $x = 1e0;
foo($x);
dd $x;    # Num $x = 2e0

However, if the coercion is applied, such as in this case:

my $x = 1;
foo($x);

Then it binds the result of the coercion to $n in foo, resulting in an error since ++ is being done on an immutable value. This is almost certainly the result of not having considered how this interaction should work.

jnthn avatar Apr 30 '19 15:04 jnthn

Initial proposal: I see two main options.

We could make it a compile-time error, so folks can't run into it accidentally. This avoids any further gotchas in this area. However, this could break working code that is successful because all the cases that exercise it pass a matching type.

Alternatively, we could define the use of a coercion type together with is rw as meaning that we should assign the result of the coercion into the passed container. This would mean that:

sub nummy(Num() $x is rw) { }
my $x = 42;
nummy($x);

Would result in $x being coerced an Int after the call. That might be OK, however there's a problem with multi-dispatch that needs a bind check. For example:

multi sub nummy(Num() $x is rw, Num $y where something-needing-nums($x, $y)) { }

Would require one, and would potentially as part of signature processing mutate $x - even in the case that the multi candidate were not selected because the where clause failed to match! It's not like you can't construct such cases today anyway, by doing side-effects in a default expression or a where clause. But it's worth considering whether that I managed to think of a surprise within 5 minutes consideration might mean there's others we didn't think of, if we try to make this DWIM.

jnthn avatar Apr 30 '19 15:04 jnthn

In this situation, I suggest that in this situation:

sub nummy(Num() $x is rw) { }
my $x = 42;
nummy($x);

The coercion is fine, since there is no type applied to $x. However in the more restrictive situation:

sub nummy(Num() $x is rw) { }
my Int $x = 42;
nummy($x);

The coercion is incorrect and should generate a run-time exception.

Thoughts?

Xliff avatar Apr 30 '19 17:04 Xliff

sub nummy(Num() $x is rw) { }
my Int $x = 42;
nummy($x);

The coercion is incorrect and should generate a run-time exception.

Thoughts?

I don't mind that behavior at all. Just a quick type check on the assignment.

my Cool $x = 42;
my Str $y = "42";
nummy($x); # no error
nummy($y); # error

I think that'd be a pretty nice if eventually we could specify coercion on assignment such that:

my Int() $x = 42;
nummy($x);

Could recoerce back to Int upon completion, if possible, and generate a run-time exception otherwise, but doing a coercing assignment would be a very different topic indeed, so I digress.

As to the concerns that Jonathan had, from a user perspective, I'd expect

multi sub nummy(Num() $x is rw, Num $y where something-needing-nums($x, $y)) { }

to work more or less like this:

sub nummy($x is rw, Num $y) {
  my Num $x-temp = $x.Num;
  die unless something-needing-nums($x, $y);
  # signature match is successful so …
  $x = $x-temp;
  # begin nummy
  ...
}

In other words, the coerced value would be temporarily stored, and only properly assigned to the rw container upon a successful signature completion.

alabamenhu avatar Apr 30 '19 18:04 alabamenhu

... The coercion is incorrect and should generate a run-time exception.

This is what I'd expect, yes.

In other words, the coerced value would be temporarily stored, and only properly assigned to the rw container upon a successful signature completion.

Signature binding is defined as processing the parameters in order. The point of the where clause in the example is that we actually expect $x to be the coerced value at that point, which it would not be with the proposed desugar. As another example, consider (Num() $x is rw, Num $y = $x), which would clearly fail if $x did not contain the Num by the point the default of $y was evaluated. I'm not convinced there's any trickery we can do with regards to delaying it that I can't come up with a counter-example for.

jnthn avatar May 11 '19 13:05 jnthn

Is there consensus that it would be good to have a general principle along the following lines?

  • If a problematic semantic is being discussed; and

  • The option to disallow the problem by newly making it a compile time failure is available; and

  • Discussion stalls before a consensus is reached on what, who, and when a better solution will be introduced (and perhaps even then);; then

  • The compile-time rejection should be considered for introduction on master as a probably appropriate immediate step; with blining to see what fall out there appears to be, and then introduction into a subsequent release, to stop the problem of code relying on the given problematic semantic becoming more entrenched.

raiph avatar Dec 26 '20 23:12 raiph

@raiph I do like the idea to introduce compiler errors when a construct is clearly problematic and serves no use. How could such a policy introduced? Is this a topic for the RSC?

patrickbkr avatar Jan 06 '21 10:01 patrickbkr

I feel like this problem is too abstract to have a good discussion about it. Best answer I could give is probably "sounds sensible, but the best course of action depends much more on the concrete semantic".

niner avatar Jan 06 '21 10:01 niner

As far as I can remember, making something a compile time error when semantics are still unclear, has been the policy. That's why we have a X::NYI exception :-)

lizmat avatar Jan 06 '21 10:01 lizmat