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

Scope context #376

Open
dphfox opened this issue Aug 31, 2024 · 6 comments
Open

Scope context #376

dphfox opened this issue Aug 31, 2024 · 6 comments
Assignees
Labels
enhancement New feature or request not ready - design wanted Good idea but needs design work

Comments

@dphfox
Copy link
Owner

dphfox commented Aug 31, 2024

One year on from the original introduction of scopes as a concept, they've single-handedly come to dominate how Fusion thinks about memory management. The reason is simple; it allows developers to define the behaviour of a whole block of code at once, with clean and explicit ways of inserting sub-blocks or isolated islands, and perpendicular ways of inheriting behaviour across those boundaries.

There's another problem for which this is useful: contextual cascading values.

In an ideal world, cascading values act identically to prop drilling: with lexical scope. This is very, very hard to do in practice - without some constraints, the problem is impossible to generally solve in Luau.

So right now, contextuals use an approximation - a call-stack-based approach - by exposing a value only for the duration of a callback. This works so long as the receiving code isn't re-entered once the callback returns - when that happens, the temporary value will have been erased by then. Additionally, other values might leak through if the contextual is set for the code causing the re-entry.

Scopes are actually a great candidate for replacing this. Scopes are "lexical-ish" because they are explicitly drilled down through components, though they're not guaranteed to be unique for each lexical scope. However, this nuance doesn't matter, because the only time scopes have to be distinct from each other is when a distinct observable effect is wanted; either a different lifetime or a different __index table.

The concept of contextual values fits neatly into this model: the observable effect is the value the contextual takes on, which is represented by a distinct scope in which the contextual has a different value.

To make this work, we will need to decide on some mechanism to inherit data into derived and inner scopes, and should consider renaming scoped to more clearly be a detached 'starting point' or 'root'.

@dphfox dphfox added enhancement New feature or request not ready - evaluating Currently gauging feedback labels Aug 31, 2024
@dphfox dphfox self-assigned this Aug 31, 2024
@dphfox
Copy link
Owner Author

dphfox commented Aug 31, 2024

One way we could do this would be to take advantage of the inherited nature of the __index table - especially if the current outer merge in deriveScopeImpl is substituted for a metatable chain to allow for dynamic contents. But this might affect the performance of method lookups for especially nested scopes, which seems undesirable.

Instead, it might be wiser to attach ancestry information to scopes, probably as part of the metatable. This can be as simple as a reference to the outer scope.

Interested code can then associate scopes with contextual values, and walk up the ancestry to learn about contextual values defined in ancestors.

@dphfox
Copy link
Owner Author

dphfox commented Aug 31, 2024

Storing ancestry information also has the potential to significantly improve lifetime analysis, by allowing analysis to flow up to common ancestors rather than having to flow down from the scopes being analysed.

@dphfox
Copy link
Owner Author

dphfox commented Aug 31, 2024

Proposed user facing API surface:

Contextual:is(value):inside(function(scope)
    -- ...
end)

The returned scope is internally weakly mapped to value.

@dphfox
Copy link
Owner Author

dphfox commented Aug 31, 2024

Alternatively, it'd be possible to ship a design that weakly maps the current scope to a value, but this is undesirable because it forces the developer to explicitly introduce scopes where they want the value to be independent. By creating the scope when a value is changed, we can create that breakpoint automatically.

@dphfox
Copy link
Owner Author

dphfox commented Aug 31, 2024

As a further consideration, :now() is not scope-aware. It would need to be given a scope to inspect.

For this purpose, we could introduce a new :here(scope) accessor.

Alternatively, we could introduce some kind of object that, when constructed in a scope, can read the Contextual, but this is heavier memory-wise and probably overengineered.

@dphfox
Copy link
Owner Author

dphfox commented Aug 31, 2024

The mechanisms for :during()/:now() and :inside()/:here() should not be mixed or interact in any way. This would introduce asymmetry (where :now() is scope-unaware but :here() is call-stack-aware) and could confuse users.

We might want to force one or the other to be used per-contextual. It's weird to want to mix them. Alternatively, call-stack-based contextuals could be removed outright.

@dphfox dphfox added not ready - design wanted Good idea but needs design work and removed not ready - evaluating Currently gauging feedback labels Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request not ready - design wanted Good idea but needs design work
Projects
None yet
Development

No branches or pull requests

1 participant