Scope Locals
Andrew Haley
aph at redhat.com
Wed Mar 31 10:10:56 UTC 2021
Just a few in-line clarifications here; I'll respond at more length
later.
On 3/31/21 1:00 AM, John Rose wrote:
> On Mar 30, 2021, at 6:33 AM, Andrew Haley <aph at redhat.com> wrote:
>>
>> In Project Loom, we've been looking at scope-local variables.
>>
>> The idea is to support something like a "special variable" in
>> Common Lisp. This is a dynamically-scoped variable which acquires a
>> value on entry to a lexical scope, and when that scope terminates, its
>> previous value (or none) is restored. We also intend to support thread
>> inheritance for scope locals, so that parallel constructs can easily
>> set a value in the outer scope before threads start.
>
> This is very good. I support this goal as a way to escape from
> the corner we’ve painted ourselves into with ThreadLocal.
>
> And I agree Common Lisp gives a good mental model, at
> least as a starting point. There are aspects of the CL model
> I’d like to question carefully before importing. The two
> main ones are (a) surface syntax for variable access:
> “naked” variable names are the access syntax for a CL
> special-variable, but that’s not appropriate for Java,
> because it impinges on the JLS which is managed very
> conservatively.
Yes, we have to use get().
> Also (b) do we need special variables to
> be side-effectible? That imports costs; it would be better
> if they were “effectively final”, with the usual provisions
> for escaping that limitation explicitly (add an indirection
> to a non-final variable).
Absolutely, yes. A scope local cannot be assigned as such, but it can be re-
bound in an inner scope.
>> One useful way to think of scope locals is as invisible parameters
>> which are passed to every method. However, we will only allow values to
>> propagate from caller to called methods: assignments to a scope local
>> variable in a called method will not be observed in the calling method,
>> because when a scope terminates, scope locals set in that scope will
>> have their previous values restored.
>
> This isolation is true of both assignments and bindings. I’d
> rather outlaw assignments completely (see above).
Yes, sorry for the unclarity.
>> We'd like to implement this without any changes to the Language or
>> Virtual Machine specifications, so a language-centric design like
>> this isn't going to happen:
>>
>> scopeLocal MyType x;
>>
>> {
>> x = value;
>> ... code that eventually calls foo()
>>
>> }
>>
>> void foo() {
>> ... code that uses x ...
>> }
>>
>> I've been kicking around various ideas and implementations of scope
>> locals for some time, and I've now got something to share with the
>> wider world, somewhat inspired by Haskell syntax, which looks
>> something like.
>>
>> let x = expr1
>> y = expr2
>> in
>> ... expression that uses x, y ...
>>
>> Translated into something a bit more Java-esque, that looks like
>>
>> // Declare to scope locals x and y
>> static final ScopeLocal<MyType> x = ... ;
>> static final ScopeLocal<MyType> y = ... ;
>>
>> {
>> ScopeLocal.set(x, expr1)
>> .set(y, expr2)
>> .in(() -> ... code that uses x and y ...)
>> }
>
> Perhaps you meant to say “code that uses x.get() and y.get()”?
Sure, thanks.
>
>> I quite like the way this looks, and it allows for some interesting
>> and useful variants. In particular, the result of the "binding
>> expression" (a list of key/value pairs):
>>
>> ScopeLocal.set(x, expr1)
>> .set(y, expr2)
>>
>> can be passed to threads, saved for later use, and so on.
>
> This raises the question of separation of concerns:
> What linkage *does* a scoping link (term here??) have to
> threads, apart from the “in” call and the “get” call?
The only thing right now is that ScopeLocals marked as inheritable
are automagically inherited by all sub-threads (except e.g. ForkJoin
worker threads, where that would be in appropriate.)
> More fundamentally, what is the type returned by
> ScopeLocal.set(…)? Is it a ScopeLocal, or is it a scoping
> link (type TBD), or are those one and the same?
It's a scoping link, yes. The details are inevitably more complex, but
it can only be used by an in() expression.
>
> Can I say:
>
> var slink = ScopeLocal.set(x, xinitval);
>
> and then call a non-static set on it:
>
> slink = slink.set(y, yinitval);
I need to say more about the API, I think.
> Can I store that “slink” in data structure for a week
> before accessing it in another thread, like this:
>
> slink.set(z, zinitval).in(() -> …code that calls get on x,y,z…)
Yes, that's the idea.
>> We'll still
>> enforce the rule that bindings cannot be *used* except inside the in()
>> method.
>
> OK, that means you can’t say this:
>
> var slink = ScopeLocal.set(x, xinitval).set(y, yinitval);
> var xval = slink.get(x);
> var yval = slink.get(y);
Right. Bindings can only be used inside an in() expression.
> But you could say this:
>
> var xval = slink.in(x::get);
Also right.
> So is there any gain from disallowing the previous API
> usages?
I'm not quite sure what you mean by "the previous API usages."
>> There are some disadvantages to this approach, in particular that this
>> method chaining can result in the creation of a bunch of temporary
>> objects. Insisting on the use of a Lambda (a Runnable or a Callable)
>> is, however, necessary in order strictly to enforce the property that
>> a binding acquires a value on entry to a lexical scope, and when that
>> scope terminates, its previous value (or none) is restored.
>
> Yes; it’s more reliable than using a TWR construct, and
> that means the JVM can optimize more “intensely”.
Exactly. So you know that the value of a ScopeLocal's get() cannot change
during the lifetime of a method. It can be re-bound inside a called
method, but that doesn't matter.
>> I'm not entirely sure about the naming here. set() is given a
>> different meaning from usual, and I'm not sure anyone would let me get
>> away with using let(). in() is slightly odd too, and perhaps something
>> more verbose would be better.
>>
>> Other possibilities are e.g.
>>
>> ScopeLocal.bind(x, expr1)
>> .bind(y, expr2)
>> .exec(() -> ... code that uses x and y ...)
>
> For bikeshed colors: I think the functional version of “setX”
> can often be usefully called “withX”. So it’s get/set for
> mutable objects, but get/with for immutable ones.
Nice, thanks.
> Your scope-link object seems to be immutable in its
> basic structure, although a “set” method could be
> applied to side-effect leaves that are already present
> due to a let/bind/with call if you disagree with my
> point (b) above. (As in Common Lisp, there are two
> kinds of potential changes: Making a new binding,
> and changing the value of an existing binding.
> That latter, if it is allowed, deserves a “set” name
> rather than a “with” name.)
No, I have no intention of allowing a ScopeLocal to be re-assigned
once bound.
--
Andrew Haley (he/him)
Java Platform Lead Engineer
Red Hat UK Ltd. <https://www.redhat.com>
https://keybase.io/andrewhaley
EAC8 43EB D3EF DB98 CC77 2FAD A5CD 6035 332F A671
More information about the loom-dev
mailing list