Skip to content

Instantly share code, notes, and snippets.

@jspahrsummers
Last active December 25, 2015 15:39
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jspahrsummers/7000136 to your computer and use it in GitHub Desktop.
Save jspahrsummers/7000136 to your computer and use it in GitHub Desktop.
Various use cases that need to be considered for ReactiveCocoa 3.0, along with their current RAC 2.x or Rx solutions

Use cases

  1. Turning an eager, pure computation into a lazy one: RACSequence, RACSignal
  2. Side-effecting work that is safe to perform multiple times: RACSignal, RACCommand
  3. Side-effecting work that is safe to perform multiple times serially, but not concurrently: RACCommand, RefCount()
    • RACScheduler would seem to apply here, but execution could become interleaved and thus not serial
  4. Side-effecting work that should only be performed once, then memoized: RACMulticastConnection with a RACReplaySubject, RACSequence
  5. Enabling/disabling a UI based on whether work can be performed: RACCommand, RACReplaySubject
    • Replaying is a Good Idea™ because this information may arrive before the UI is ready for it
  6. Updating a UI with information about work in progress: RACCommand, RACSubject
  7. Requesting work, then receiving a response with values: RACCommand, creating a RACSignal and capturing arguments, RACChannel
  8. Two-way bindings: RACChannel
    • When validation is used, this sorta ends up looking like bidirectional request-response
  9. Receiving values from an event source that's always alive (e.g., mouse position): RACSubject

Questions

  1. For each of the above, what does cancellation mean, if anything?
  2. Are there any use cases that we should avoid solving? Maybe some of them belong somewhere other than RAC.
  3. How do we design an API that provides exactly one solution for each use case? For complex problems that meet more than one of the use cases above, our solutions should be small, purpose-specific, and composable, instead of trying to solve all the things at once.

Ideas

  • Focus on cold signals.
  • Apply backpressure from consumers to producers.
  • Bring RACMulticastConnection (possibly renamed) front and center as a way to perform memoized side effects. Remove or seriously de-emphasize subjects, and remove operators like -replay in favor of using connections explicitly.
  • Replace RACCommand with simpler and more composable Kleisli arrows.
  • "Enabledness" operator: returns a signal of enabledness, and the original signal, except the latter will error upon subscription if disabled (a la -[RACCommand execute:]).

Proposed RAC 3.0 equivalents

  1. RACSignal
  2. RACSignal
  3. -shareWhileActive
  4. RACPromise
  5. RACSignal
  6. RACAction
  7. Creating a RACSignal and capturing arguments
  8. RACChannel
  9. RACSubject

Replacements

  • RACSequence is mostly replaced with RACSignal
  • RACReplaySubject behavior is moved into RACMulticastConnection (or equivalent)
  • RACBehaviorSubject is eliminated
  • RACCommand is broken down into:
    • RACAction
    • -shareWhileActive
    • More use of plain RACSignal
@jonsterling
Copy link

I really really like the idea of forcing memoization to be explicit with multicast connections. Whilst I don't think the replay stuff is the most egregious aspect of RAC today, I would argue that it is the most confusing! Particularly, how unfortunate it is when you can't tell if a signal is yet memoized from the type...

I found myself sort of just throwing replays around without thinking very carefully about it, sometimes to unfortunate effect. Looking forward to being forced into more principled approach! :)

In addition, though I think I may have mentioned this elsewhere, I'd like to motivate the idea of breaking the kleisli arrows away from the rest of the command stuff for people who've not had the benefit of our various conversations on the topic.

Whilst one certainly doesn't need to have a use-case in mind to see the importance of having a principled notion of functions A -> Signal B which you can do stuff with, I have in fact found them absolutely necessary in the process of implementing ReactiveFormlets 1.5. Initially, validators were just pure functions that took some data, and returned either a certificate of success or a condemnation; eventually, in using RAF with a certain product I'm building at work, I came to the point where I actually needed to perform work asynchronously inside of a validator. That is, the validator was no longer morally a function A -> Validation A, but it was in fact a function A -> Signal (Validation A). Of course, pure validators can be recovered by throwing a +[RACSignal return:] in the function body.

So I implemented this, and it sadly took me more than a month to realize that these fancy validators could be replaced with RACCommand (with concurrency enabled). Unfortunately, RACCommand also provides a bunch of junk that I didn't need: disabling, execution tracking, etc. So, this is a nice example-case for why it might be good to have something with the spirit of RACCommand, but not its extra bones.

Of course, the benefits of adding precomposition to commands (or whatever replaces them) cannot be understated. For instance, when the view model expects a certain kind of data to be passed to a command, and yet we have a control which lets you set a command, but expects to have a different kind of input, we must find some way to glue these together! A pullback will suffice quite nicely to translate between the two. I have not yet found a use-case for postcomposition in my current codebase, but I suspect we shall find that useful as well.

@jspahrsummers
Copy link
Author

@jonsterling To be clear: in my current vision, RACCommand wouldn't exist in post-3.0 code. All of its functionality would instead be broken down and distributed into other, simpler abstractions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment