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

Future Roadmap for Effection #204

Closed
jnicklas opened this issue Oct 23, 2020 · 4 comments
Closed

Future Roadmap for Effection #204

jnicklas opened this issue Oct 23, 2020 · 4 comments
Assignees
Labels
question Further information is requested

Comments

@jnicklas
Copy link
Collaborator

We're nearing the beta release of BigTest, and once that has settled, IMO we should also start condsidering the path forward for Effection, and how we proceed with both the current version of Effection and a possible future version.

Let's recap some of the issues of the current version of Effection, and why we might want to make some fundamental changes:

  1. Control functions: Currently the basic unit of an operation is the control function, which takes a context as an argument and can interact with this context directly, for example by spawning new children, halting, or attaching exit handlers. The problem with control functions is that they are not limited at all in what they can do with a context. Due to a lack of fundamental power in the abstractions offered, we've had to resort to various hacks which abuse the fact that control functions can manipulate the context directly. This is problematic, because the Context's API is a mish-mash of interfaces, some of which are meant to be internal and some which are not. Therefore it is very easy to misuse these APIs and it currently takes expert-level knowledge of the Effection internals to be able to know which APIs are safe to call under which circumstances.
  2. Ambiguous operation types: This ties into (1). There is an inherit ambiguity in the operation types. There is not really any good way of distinguishing a control function from a generator function. We've employed various hacks to attempt to do this safely, but it is quite clear that this is brittle, and for example slight differences in how the code is bundled can cause mysterious, hard to debug issues.
  3. Unclear whether yield is async: For all of its faults (and there are many), one thing that the promise spec does get right is that all promises are always asynchronous. Even await Promise.resolve(3) actually suspends and continues in the next iteration of the event loop. Effection currently allows yield to be synchronous as long as resume is always called synchronously. Not only does this lead to issues like Forks that throw immediately try to abort their parent while the parent is still running #26, but it is also dangerous and confusing to users. If a yield is always synchronous, a user will have to be aware of this fact and build their code around this. The current situation encourages users to rely on the fact that yield can be synchronous, but if the called code ever does decide to suspend then this has the potential of introducing subtle bugs into the program.
  4. Spawning as an operation: Starting with Convert fork into an operation #57 we have treated spawning as an operation. Given (3) this leads to very problematic situations where a lot of our code in BigTest relies on the fact that yield is synchronous when spawning. It's worth pointing out that there is no inherent reason for spawn to be an operation! Even today there is a non-operation API for spawn via control functions, it is just not encouraged to be used. The same can be said for subscriptions. There is no reason for creating a subscription to be an async operation.
  5. Poor integration with existing promise and callback based code: Today's Effection needs to resolve to various hacks to integrate well with promise and callback based code. The fact of the matter is that there is a large ecosystem of JavaScript libraries out there, and in BigTest we've had to do a lot of work to integrate well with them. It feels like there should be a more straightforward path of doing this.
  6. Asynchronous teardown: There are many situations where halting cannot be synchronous, yet present day Effection assumes that halting is always synchronous. Asynchronous requires quite drastic changes to the execution model.
  7. Resources: with resources we solved the problem presented in Returning with an associated supended context makes it outlive its scope #86. However, while resources are in some ways really great and magical, the problem is when that magic breaks down. While returning resources works great, composing resources is very tricky and very error prone. An operation which creates two resources and wants to return both of them is very difficult to write. The problem we tried to solve with resources was that implicit scopes are not carried forward, it has become increasingly clear that the better solution to this is to not use implicit scopes in the first place. Making it explicit which scope spawn occurs in, rather than using the current scope, solves the same issue that resources were meant to solve with a lot less magic and a lot less connfusion, at the expense of being more verbose and explicit.
  8. TypeScript: Current Effection is written in JavaScript, but TypeScript is great, so it would be nice to have Effection written in TypeScript.

Given all of these issues, it is pretty clear that while Effection has been great and very useful for BigTest, there are some fundamental changes which will require some very fundamental changes at least, and a complete rewrite probably.

We've been aware of these issues for a while and so have been working on a replacement for Effection in the https://github.com/jnicklas/mini-effection/ repo. In #194 we did a proof of concept for whether this version of Effection could replace the current functionality in the effection monorepo and this experiment was largely successful.

My proposal is as following:

  1. Work out which issues currently stand in the way of releasing the current state of Effection as v1
  2. Solve these issues and release v1
  3. Freeze development of v1 and start integrating mini-effection into the repo as v2 of Effection.
  4. Move to v2 of Effection in BigTest. This will require a pretty massive change, mostly to remove resources, but while this change will be a bit of work, based on the experience of V2 Proof of Concept #194 it should not be terribly difficult.
@jnicklas jnicklas added the question Further information is requested label Oct 23, 2020
@cowboyd
Copy link
Member

cowboyd commented Oct 23, 2020

Thanks for kicking this off! I'm excited to start working on v2 😄

What issues do we see as blocking the v1 release? I don't think we've had any major breaking API changes in a really long time, and I don't see that there is an urgent need for it right now, so it might make sense to release v1 as-is?

I agree with the core proposal, and would like to include in the v2 plan some secondary issues / questions.

  • How do we handle libraries? What breaking v2 changes need to be made to the libraries at the same time. Some examples I have in my head are things like breaking Channel into Channel and Port so that you can hand out references to things that can only publish things, or consolidating @effection/{events, channel, subscription} into a single library.
  • We want to rename a bunch of things especially around Subscriptions. Do we do that now, or later?

Finally, I've recently found the 7GUIs site and really like the approach of trying to identify the use-cases that highlight the heart of the problems. I think now that we've had a lot of experience with structured concurrency and the problems it solves (and in our case, creates because of deficiencies in the library!) I think we can outline our own tasks that can serve both as a guide for evaluating v2 design decisions, and also as documentation that we can publish directly.

Of course it doesn't need to be exactly 7, but these were the first that occured to me:

  1. Handling timeouts
  2. composing operations with side-effects (installing, un-installing listeners, starting, stopping servers)
  3. concurrent operation composition. E.g. how to integrate the return values of asynchronous operations similar to the way you would use Promise.all() or Promise.race().
  4. composing server process like we do in the orchestrator (might be a sub-point of (2), but we see a pattern with servers of closing over an operation and executing it multiple times, although it seems like we've started moving servers over to external iteration... for example subscribing to a stream of requests.)
  5. implicit context for things like authentication, logging and other cross-cutting concerns. We don't really do this at the moment, but I feel pretty confident that we'll want to at some point, so we have to be sure we don't paint ourselves into any corners
  6. transparency of the runtime state for things like operation tree traces (structural analogue of stack traces), visual inspectors, maybe even step through debugging. Again, we don't have to do this in order to begin development for v2, but I think it needs to be on our road-map because v1 is somewhat frustrating in this regard.
  7. integration with external systems. Ports? Channels? Spawn points?
  8. supervision of concurrent operations: restartable, cancelable, queueing of operations. The live demos on ember-concurrency give some great examples of this.

If we can fill out this list with anything that's missing, and then condense it so that we have either an implementation or a plausible plan for all of them, then I think we can be very confident that v2 will be able to do whatever anybody cares to throw at it. I imagine that we'll want v2 to be the version that we begin to evangelize and that we end up using for a very long time, and so I'd like to begin with great evidence for why it's awesome both to validate the design, but also to make the case to the public.

@jnicklas
Copy link
Collaborator Author

Very good question regarding renaming things. If we already align things towards the naming we want in v2, then we can make those changes now, and have less churn when we eventually move to v2, which will be more focused around the actual functionality, rather than naming changes.

Here are the things that I renamed in #194:

  • Context -> Task: I just don't like the name Context, it's too generic and not descriptive to me. The fact that Rust uses Task for a similar concept and ember-concurrency also uses it for an admittedly slightly different, but still related concept makes it feel like the correct name to me.
  • Subscription -> OperationIterator: In practice we just don't use raw subscriptions very often, and so reserving such a "nice" name for them feels a bit backwards. This naming also aligns with Iterator and AsyncIterator, the already existing interfaces with similar functionality.
  • Subscribable -> OperationSubscribable: Again, this aligns with the Iterable and AsyncIterable interfaces which already exist.
  • ChainableSubscription -> Subscription: This gives the interface that we do use in practice a nicer name.

I've created #205 to discuss a possible split of Channel into its sending and receiving end. IMO this is a good idea, and might be something we want to do before releasing v1.

Great list of design considerations!

I think especially (5) and (6) are really great inclusions, since I did not give them any consideration in the design of mini-effection.

I think answering a question like "what is the state of Effection right now" is very interesting, and we should definitely spend some time thinking about how best to answer such a question.

For example, one small thing that I've thought about is the ability to name spawned tasks:

function *server(handler: ...) {
  // ...
  task.spawn(`handler for request ${request.id}`, handler(request));
}

There are a lot of things to consider with making Effection more easily debuggable.

@jnicklas
Copy link
Collaborator Author

A lot of the questions surrounding the naming of things in for example @effection/channel. I just had a thought. Can we hit 1.0 on effection itself without shipping 1.0 of the secondary packages like channel? This way we don't have to settle on an API on these component libraries until we feel confident about them.

What do you think?

@cowboyd
Copy link
Member

cowboyd commented Jan 29, 2021

I think that makes sense. One thing that I was thinking recently while trying to walk some folks through using Effection for the first time was that in order to accomplish anything non-trivial, you have to reach for several packages and know where to look for them. @effection/subscription, @effection/channel, and @effection/events are foundational to almost every single effection application. It will make it a lot less confusing and spur adoption if we can eventually import them all directly from the effection package proper.

Perhaps what we should do is develop the new mini-effection based effection in @effection/core and then, when ready, just re-export all of the symbols from there in effection as 1.0

Then, as we settle on what @effection/subscription looks like, we can add it to the aggregator package effection as 1.1, etc...

As we commit to each sub package's api, we can add it as a minor release to the effection package.

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

No branches or pull requests

2 participants