guy.steele at oracle.com
Wed Mar 10 18:52:18 UTC 2021
> On Mar 10, 2021, at 11:40 AM, Brian Goetz <brian.goetz at oracle.com> wrote:
>> Using & between patterns is unfortunate given that if if first pattern is not total and does not match, we will not "execute" the second pattern.
>> So the semantics is lazy but currently '&' means non-lazy on expressions.
> While I understand the discomfort that drives you to make this observation, I think we should be a little more careful with our terms; lazy is not the right word here.
Yes, “lazy” is not quite the right word here, but I suspect Rémi intends to use that word to mean what you and I use the term “short-circuiting” to refer to.
The notions are of course closely related, because in a lazy language, short-circuiting falls out naturally from a straightforward definition such as
a && b = a ? b : false
But of course Java is not a lazy language. One can instead model short-circuiting behavior in terms of lambdas:
a && b is an abbreviation for conditionalAnd(a, () -> b)
conditionalAnd(x, y) = x ? y() : false
Nevertheless, Rémi’s main point remains: it is true that `&&` makes the average Java programmer think of conditional execution and `&` does not. That makes me a bit uncomfortable also, but for me, at least, that discomfort is outweighed by other considerations. There are significant advantages to using distinct symbols for pattern conjunction and guard attachment, and, despite having proposed the use of `&&&` earlier, I find I cannot stomach it myself—it would be too verbose a symbol for pattern conjunction. So I regard the choice of `&` for pattern conjunction as a compromise, but an acceptable one, largely because I think that in practice programmers will rarely rely on the conditional-execution aspect of pattern conjunction.
> (When used this way, *ALL* patterns are lazy! A pattern is not an expression; it is not "evaluated" like expressions are. It is a _recipe_ for a deferred computation (like a lambda.))
> When we say `x instanceof (P&Q)`, we "construct" the composite pattern P&Q _before we "execute" it_. What are the semantics of such a combined pattern? It is short-circuiting; when we later compute whether a target matches the pattern, we first try to match it to the first subpattern, and if that succeeds, we try to match it to the second subpattern. This is not "lazy evaluation", this is just the semantics of pattern composition with AND. Short-circuiting is a more accurate description here than laziness.
> As an analogy, imagine we had intersection types in instanceof (we already do for casts, so this is not much of a leap). If we asked `x instanceof T&U`, and x was not an instanceof T, would we insist that we also test against U (which could throw if the JVM can't find U.class to load!) Or would we accept that a test against the intersection type T&U can reasonably "short circuit" the test if it fails the first one? Of course we would. Why is it different for patterns, which are a generalization of this kind of test?
One reason it is different for patterns is that patterns can contain expressions (arguments for any input parameter). Unless we are in a position to enforce that such expressions are free of side effects, the programmer will have an interest in being sure that pattern conjunction either definitely short-circuits or definitely does not short-circuit. In the case of intersection types, it’s just a speed optimization.
> Continuing on the "like a lambda" analogy, if pattern refers to variables in the current scope as one of its "inputs", this is like a capturing lambda. And, like a lambda, when we were looking at `true(e)`, we said that any locals in `e` should be effectively final.
> So to propagate that constraint into the current model: in a guarded pattern P&&g, all locals referred to in `g` should be effectively final. (We've already said this of pattern input parameters in general.)
More information about the amber-spec-experts