Reactive Code is Sequentially Cohesive

Reactive, declarative code is sequentially cohesive: you have a sequence of events and reactions to events, and it’s pieces are tied together real close.

The processing chain itself is then a function or feature of the app. The chain of operators is a thing itself; the step-by-step transformation is then reified into a sequence in one place: it has a beginning, an end, a sequential order of steps.

That makes the encapsulation of the sequence is very clear.

Compared to some object-oriented factorings of code, reactive code is often very good at showing such sequences in one place. It’s “data-driven” inasmuch as you transform data from one format to another, and eventually produce a result or side effect.

With traditional object-oriented factoring techniques, you might be inclined to split up multiple transformational steps into isolated objects, then either couple them sequentially so information flows from object to object by virtue of delegation from A→B→C→D, which is hard to follow and see at a glance, or have a “master sequence” call each object in succession and pipe the results from one to another.

Back when I took note of this in 2016, Rx was all the rage and looked somewhat different. To preserve the historic example that even predates Swift.URL, here’s the original live search text field by Marin Todorov that shows the sequential nature of steps:

query.rx_text

  .filter { string in
      return string.characters.count > 3
  }

  // Coalesce rapid-firing of events
  .debounce(0.51, scheduler: MainScheduler.instance)

  .map { string in
      let apiURL = NSURL(string: "https://api.github.com/q?=" + string)!
      return NSURLRequest(URL: apiURL)
  }

  .flatMapLatest { request in
      return NSURLSession.sharedSession().rx_data(request)
  }

  .map { data > Array<AnyObject> in
      let json = try NSJSONSerialization.JSONObjectWithData(data,
          options: [])
      return json as! Array<AnyObject>
  }

  .map { object in
      return Repo(object: object)
  }

  .bindTo(tableView.rx_itemsWithCellIdentifier("Cell"))

A traditional approach might look like this, for example:

@IBOutlet var tableView: ReposTableView?
lazy var debouncer = Debouncer(interval: 0.51, delegate: self)
lazy var gitHubAPI = GitHubAPI()
lazy var jsonDecoder = JSONDecoder()

// Entry point
func handleTextChange(string: String) {
    guard string.count > 3 else { return }
    debouncer.debounce(string)
}

// DebouncerDelegate callback
func debouncerDidDebounce(_ debouncer: Debouncer, string: String) {
    gitHubAPI.repos(forQuery: string) { [weak self] result in
        switch result {
        case .success(let repos):
            self?.tableView.repos = repos
        case .failure(_):
            errorHandlingIsAnExerciseForTheReader()
    }
}

You see how employing different objects here requires some back and forth already, to hand things off from the caller to the Debouncer, then get back a debounced result at some later time via a callback: It’s already 2 functions you need to look at, 2 places that are part of a sequence, but the middle portion is invisible here. The part that glues handleTextChange to debouncerDidDebounce is not apparent. That’s part of the pain of splitting code into multiple functions or event objects.

You might think that it’d help to make the debouncer block-based – but since the debouncing is stateful, it makes more sense to have one shared callback, too. So you’d pass the block in the Debouncer.init, not at the call site in debounce, and still end up with 2 places you need to look at.

It actually is quite nice, though, that GitHubAPI encapsulates the processing of search strings to URL queries to Data results to JSON and then to Repo objects. So there’s a sub-sequence hidden inside the repos(forQuery:) call. (That’s totally possible in the reactive code as well, of course, but would obfuscate the nature of the example Marin provided.)