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

Scopes #187

Open
doeixd opened this issue Apr 22, 2024 · 3 comments
Open

Scopes #187

doeixd opened this issue Apr 22, 2024 · 3 comments

Comments

@doeixd
Copy link

doeixd commented Apr 22, 2024

Right now, all the signals share the same hidden state and are apart of the same reactivity system.

I was wondering why this shouldn't be made explicit, to control the hidden state, and group together sub-graphs #159 , to possibly allow for different reactive algorithms, bulk deallocation/garbage collection, better dev tools, and labeling of the reactive graph, or maybe things like Relays?

I'm not a JS expert. But maybe this could look like:

// The previously hidden global state and reactive runtime, will be moved into the scope.
const scope = new Scope({
  algorithm: 'default',
  name: 'default',
  dispose: () => {}
})

// There could be a default global scope to make the api more ergonomic.
const value = new Signal('value', { scope })

// You can view the scope here = value[Signal.subtle].scope 

It's just an idea. Let me know what you think.

Prior Art
OCaml Incremental Scopes
Leptos Runtimes
SolidJS Root

@shaylew
Copy link
Collaborator

shaylew commented Apr 23, 2024

This is definitely worth considering!

It would be a bit more flexible and less magical to be able to have multiple signal graphs rather than one global state. On the other hand, it would seriously harm any hope of interop for frameworks to be using different signal graphs without shared autotracking, so this capability could easily turn into a footgun. If we did have multiple graphs, we might need to come up with a compelling story for how you're supposed to intentionally bridge between two signal graphs if you want to use them in concert. (That's not necessarily a bad thing -- I think system boundaries like Watcher are where the current proposal is weakest, and having the boundary APIs be up to the task of stitching two separate signal graphs together seamlessly after the fact would be proof of a sort that the boundary APIs were sufficiently expressive.)

I think in Incremental the corresponding concept would be the Incremental.Make generative functor, which mints an entirely new Incremental module with distinct types -- scopes are a different thing, and multiple scopes do live within the same dependency graph. I hadn't seen Leptos's runtimes before but it looks like more or less the same idea as Incremental's, just encoded via CPS for lack of existential types.

Solid's idea of multiple roots isn't really in the same space; Solid keeps only one global state, and any source you read (regardless of what root it was created in) can be autotracked by any memo or effect.

@dead-claudia
Copy link
Contributor

I didn't see this when I made the comment, but I just realized the long framework hypothetical in the third paragraph in #195 (comment) is alerting to encapsulation issues in the current design that (in part) requires much of this very thing to solve.

I approached it from a different angle, but ultimately, it boils down to a need for scoping, just for consistency across watcher composition.

@guitarino
Copy link

guitarino commented May 4, 2024

We have a need for something like scopes, and I'm not sure how we'd be able to implement it without them

We have a scenario where we an application uses signals for its internal state, as well as exposes some of them to an external user. However, it's important for the application to control the timing of when signals get updated - it is a 3D application that needs to update its internal state during a frame, and only allow the external user to react to updates after the frame update

The way I would do it with scopes is:

  • The application and its user would have 2 different scopes
  • The application would take the user's scope as an argument
  • For every signal it wants to expose, there would be
    • a signal in the user's scope, and
    • a watcher in the application's scope that depends on the internal signal and updates the corresponding user's signal during a scheduled update

MobX has the ability to isolate global state, so this feature can also be important in order for the existing libraries to start using the native signals internally

Additionally, due to the API's ability to return signals contained in watchers and computeds via introspectSources, it is somewhat of a security concern that signal scopes cannot be isolated from each other. If a malicious library got an access to a function that reads certain signals, by calling that function inside a watcher, they'd be able to find out the contents of those signals. With scopes, the users would be able to safeguard their signals against watchers and computeds that don't share the same scope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants