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

[WIP] Imperative hook delegation and functional hook APIs through StateHooks #39

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

sugarmanz
Copy link
Collaborator

@sugarmanz sugarmanz commented May 21, 2024

What Changed

Currently, all the code lives in a test to act as a POC. The basic approach is to add a new StateHook concept (somewhat akin to Kotlin coroutines StateFlow) for encapsulating values published by a single arity, Unit return sync hook:

val hook: SyncHook<(HookContext, T) -> Unit>

The typing is important here because we can only arbitrarily tap hooks that we know what to return with. This leaves Unit (effectively void), BailResult, and T. Leaving out BailResult (as bail hooks aren't likely to be used to represent state) & T (waterfall hooks are a little more probable, but still not likely to be used to represent state).

// imperative hook API through delegation
val value: T? by hook.asStateHook()

// imperative hook API through property access
val stateHook = hook.asStateHook()
stateHook.value

I attempted to solve imperative hook APIs first, but found that the use case laid out in #23 somewhat requires some functional APIs to flatMap nested hooks; flatMap just meaning mapping incoming hook values into other hooks to unwrap as part of a StateHook. All the functional APIs are meant to act as reactive chunks to compose hooks, i.e. they all return new hooks that wrap the parent hooks, with some unique logic to power such functionality. The current functional APIs implemented in this POC:

  • filter: only propagate hook values that satisfy the predicate
  • map: transform incoming hook value
  • flatMap: transform incoming hook value into another hook and unwrap
  • flatten: flatMap where identify is the incoming hook value

The test code has a few example tests, as well as edge case tests. It'd be easiest to look at these tests to understand how they should be used:

  1. simple state hook: Demonstrates ability to delegate to a StateHook for state representation in a variable. Also demonstrates "replay-ability" of StateHooks to ensure new taps are called with the state value, even if a new value isn't published.
  2. map state hook: Demonstrates ability to map hook values to encapsulate arbitrary information as state
  3. flatmap state hook: Demonstrates ability to flatMap nested hooks

The other tests are a bit more pervasive edge case tests to ensure the API works for all use cases. Feel free to take a look, but it gets a messy quick.

With that, there is still a complexity around nullable hook values and nullable hooks. flatMap vs flatMapNullable exists strictly to ensure we respect the incoming hook value nullability with respect to the new hooks type parameters, nullable or not. Kotlin allows us to represent both in an overloaded generic, but the JVM is unable to represent such distinction, hence the separate methods. I am somewhat of the mind to create a new sealed class to encapsulate the return type of StateHooks, to represent "empty" vs a stateful value. This'd allow null to strictly represent a state value, at least for the hook API. Delegation could still use null to represent an empty state, as we wouldn't want to pollute the convenience API with more complexity.

Why

Solving for #23 & #37

  • Add tests
  • Add docs
  • Add release notes

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

Successfully merging this pull request may close these issues.

1 participant