From 6dab204621d7f12f84e3ec59019b115252c8b75b Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Wed, 11 Aug 2021 11:59:00 -0400 Subject: [PATCH 1/3] invokeHelper argument-based thunks --- text/0000-invokeHelper-property-thunks.md | 176 ++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 text/0000-invokeHelper-property-thunks.md diff --git a/text/0000-invokeHelper-property-thunks.md b/text/0000-invokeHelper-property-thunks.md new file mode 100644 index 0000000000..448cb16bba --- /dev/null +++ b/text/0000-invokeHelper-property-thunks.md @@ -0,0 +1,176 @@ +--- +Stage: Accepted +Start Date: 2021-08-11 +Release Date: Unreleased +Release Versions: + ember-source: vX.Y.Z + ember-data: vX.Y.Z +Relevant Team(s): Ember.js +RFC PR: +--- + + + +# Add argument-based thunks to invokeHelper + +## Summary + +`invokeHelper` (from `@ember/helper` (re-export from `@glimmer/runtime`), introduced in [RFC 626](https://github.com/emberjs/rfcs/pull/626)) +is a low-level utility for helping library-authors create reactive wrapping abstractions, such as _Resources_. + +However, `invokeHelper`'s third argument, the thunk, entangles all tracked data during creation +of the helper and does not allow lazy-entanglement upon access. This RFC proposes a solution +to enable lazy-entanglement of tracked data when using `invokeHelper`. + +```js +import Component from '@glimmer/component'; +import { cached } from '@glimmer/tracking'; +import { invokeHelper } from '@ember/helper'; +import { getValue } from '@glimmer/tracking/primitives/cache'; + +export default class MyComponent extends Component { + @tracked op = '+'; + @tracked left = 1; + @tracked right = 2; + + calculator = invokeHelper(this, Calculator, { + named: { + operation: () => this.op; + }, + positional: [ + () => this.left, + () => this.right, + ], + }); + + get calculatorValue() { + return getValue(this.calculator); + } +} + +// Assume there is a Helper manager registered that knows what to do with this +// and getValue(this.calculator) returns an instance of this class +// (instead of calling compute like on the default Helper class) +class Calculator { + @cached + get leftDoubled() { + console.log('doubling the left'); + return this.args.positional[0] * 2; + } + + @cached + get rightDoubled() { + console.log('doubling the right'); + return this.args.positional[1] * 2; + } + + get result() { + if (this.args.named.op === '+') { + return this.leftDoubled + this.rightDoubled; + } + + return this.leftDoubled - this.leftDoubled; + } +} +``` +```hbs +{{this.calculatorValue.result}} => prints 6, both console.logs print +{{!-- sometime later this.left changes to 3 --}} +{{this.calculatorValue.result}} => would print 10, only one console.log prints (because the left changed and not the right) +``` + +prior to this RFC's implementation, when _any_ arg changes, everything is invalidated and usage of the +`@cached` decorator is moot, whereas intuition states that only changes to arguments accessed within +the getter are consumed. + +## Motivation + +One of the benefits of tracking, and by proxy, auto-tracking, is that data is lazily entangled. +_You only pay for what you use or consume_. However, this is not the case with `invokeHelper`. +When using `invokeHelper`, all tracked data accessed/consumed in the third arg, the thunk, is +entangled with the entirety of the helper (passed to `invokeHelper`'s second arg). In order to +provide the same lazy-entanglement benefits we have in the rest of the framework, `invokeHelper`, +needs to also support lazy-entanglement per-argument (both named and positional). + +## Detailed design + +This is a non-breaking, additive change to `invokeHelper`'s third argument, the thunk. +Currently, `invokeHelper`'s thunk is a single thunk: +```js +invokeHelper(context, helper, () => { + return { + positional: [valA, valB], + named: { + namedArg: valC, + }, + }; +}); +``` +This RFC Proposes the following alternative API: +```js +invokeHelper(context, helper, { + positional: [ + () => valA, + () => valB, + ], + named: { + namedArg: () => valC, + } +}); +``` + +This change would occur in [`@glimmer/runtime/lib/helpers/invoke.ts`](https://github.com/glimmerjs/glimmer-vm/blob/master/packages/%40glimmer/runtime/lib/helpers/invoke.ts#L48) +where the the `computeArgs` type would change: +```diff + export function invokeHelper( + context: object, + definition: object, +- computeArgs?: (context: object) => Partial ++ computeArgs?: ++ | (context: object) => Partial ++ | Partial<{ ++ positional: Array<(context: object) => unknown> ++ named: Record unknown> ++ }> + ): Cache { +``` + +later on in the `invokeHelper` function, +```diff +- let args = new SimpleArgsProxy(context, computeArgs); ++ let args; ++ if (typeof computeArgs === 'function') { ++ args = new SimpleArgsProxy(content, computeArgs); ++ } else { ++ // details tbd ++ args = new DetailedArgThunksProxy(content, computeArgs); ++ } +``` +The (name tbd) `DetailedArgThunksProxy` would lazily evaluate each of the argument thunks on +access of the specific argument accessed. + + +## How we teach this + +- Update docs with examples in the doc-comment block in `@ember/helper/index.ts` + +## Drawbacks + +- It looks verbose / awkward + +## Alternatives + +TBD? + +## Unresolved questions + +TBD? From 06823d55b4329c9264871d5303d29c80705fe6b5 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Wed, 11 Aug 2021 12:00:26 -0400 Subject: [PATCH 2/3] Update RFC Details --- ...-property-thunks.md => 0762-invokeHelper-property-thunks.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-invokeHelper-property-thunks.md => 0762-invokeHelper-property-thunks.md} (99%) diff --git a/text/0000-invokeHelper-property-thunks.md b/text/0762-invokeHelper-property-thunks.md similarity index 99% rename from text/0000-invokeHelper-property-thunks.md rename to text/0762-invokeHelper-property-thunks.md index 448cb16bba..4f92315673 100644 --- a/text/0000-invokeHelper-property-thunks.md +++ b/text/0762-invokeHelper-property-thunks.md @@ -6,7 +6,7 @@ Release Versions: ember-source: vX.Y.Z ember-data: vX.Y.Z Relevant Team(s): Ember.js -RFC PR: +RFC PR: https://github.com/emberjs/rfcs/pull/762 ---