Help us understand the problem. What is going on with this article?

bindToとかaddDisposableToとか鬱陶しくない?演算子に置き換えちゃえ!

More than 3 years have passed since last update.

はじめに

RxSwift Advent Calendar が空いていたので、ちょっと思いついたことを記事にしてみました。

これはバッドノウハウかもしれません。でも嬉しいって人は多いはず!

やりたいこと

RxSwift を使っていると、以下のようなコードがよく出てきます。

presenter.buttonTitle.bindTo(button.rx.title)
  .addDisposableTo(disposeBag)
presenter.labelText.bindTo(label.rx.text)
  .addDisposableTo(disposeBag)

この bindTo とか、特に addDisposableTo とかを演算子に置き換えて

presenter.buttonTitle |> button.rx.title +> disposeBag
presenter.labelText |> label.rx.text +> disposeBag

って書けたら楽だし見やすくない?という思いつきです。

これはちょっとやり過ぎって言うなら、せめて addDisposableTo をまとめて書けるようにしたい!

disposeBag.addDisposables(
  presenter.buttonTitle.bindTo(button.rx.title),
  presenter.labelText.bindTo(label.rx.text)
)

実装

手元の環境がまだ Xcode 7.3 で Swift 3 が使えません。なので Swift 2.2 で動作確認しています。以下をどこかに書いておくだけです。

infix operator |> {associativity left precedence 140}
infix operator +> {associativity left precedence 140}

func |> <L: ObservableType, R: ObserverType where R.E == L.E>(lhs: L, rhs: R) -> Disposable {
  return lhs.bindTo(rhs)
}

func |> <L: ObservableType>(lhs: L, rhs: Variable<L.E>) -> Disposable {
  return lhs.bindTo(rhs)
}

func +> (lhs: Disposable, rhs: DisposeBag) {
  lhs.addDisposableTo(rhs)
}

演算子は存在しないものを新たに定義して使っています。既に存在するものは「期待する動作」があるわけなので、それとは違う意味を持つ演算子は新たなものを用意した方がいいと思います。演算子の優先順位は全く何も考えずに適当に140を設定しています。これは Swift演算子まとめ によると + や - と同じになるみたいです。

Swift 3.0 では演算子の定義は型に所属させるので、拡張する場合は extension の中に書くみたいです。またジェネリクスの where を後ろに持っていけます。

infix operator |>: AdditionPrecedence
infix operator +>: AdditionPrecedence

extension ObservableType {
  static func |> <L: ObservableType, R: ObserverType>(lhs: L, rhs: R) -> Disposable
      where R.E == L.E {
    return lhs.bindTo(rhs)
  }

  static func |> <L: ObservableType, R: ObserverType>(lhs: L, rhs: R) -> Disposable
      where R.E == L.E? {
    return lhs.bindTo(rhs)
  }

  static func |> <L: ObservableType>(lhs: L, rhs: Variable<L.E>) -> Disposable {
    return lhs.bindTo(rhs)
  }
}

extension Disposable {
  static func +> (lhs: Disposable, rhs: DisposeBag) {
    lhs.addDisposableTo(rhs)
  }
}

演算子の優先順位は + や - と同じになっていますが、これも何も考えずに Swift 公式の解説 のコードを真似しただけです。コンパイルが通るかすら確認していないので、間違いがあるかもしれません。

利点・欠点

bindTo や addDisposableTo は(RxSwift 知らないと厳しいかもしれませんが)初見で何をやっているか言葉から推測できますし、RxSwiftを知っている人には理解できます。しかし記号にしてしまうと、記号の意味を知っている人にしか分かりません。

また Swift には記述時の文字数の少なさよりも言葉が明確なことを好む文化があります。なので最初にバッドノウハウかもしれませんと書きました。

しかしソースコードを読み書きをするチームメンバー全員がわかっているのなら、むしろ区切りが明確になり大事な部分だけが目立つので、読みやすく分かりやすいと感じるのではないでしょうか?

addDisposables版

さすがに演算子にするのはちょっとやりすぎだと思うなぁという人には、以下の拡張が便利かと思います。

extension DisposeBag {
  func addDisposables(disposables: Disposable...) {
    for disposable in disposables {
      disposable.addDisposableTo(self)
    }
  }
}

Driverのdriveも

演算子化するなら Driver の drive にも同じように演算子を用意したいですね。bindTo とは別の演算子にすることで、Presenter に対して Driver で提供することを強制できます。Swift 2.2, RxSwift 2.6 の場合は以下でコンパイルが通りました。

infix operator *> {associativity left precedence 140}

func *> <L: DriverConvertibleType, R: ObserverType where R.E == L.E>(lhs: L, rhs: R) -> Disposable {
  return lhs.drive(rhs)
}

func *> <L: DriverConvertibleType>(lhs: L, rhs: Variable<L.E>) -> Disposable {
  return lhs.drive(rhs)
}

Swift 3.0, RxSwift 3.0 の場合は、以下のようになりそうですが、コンパイルの確認すらできていません。

infix operator *>: AdditionPrecedence

extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingStrategy {
  static func *> <L: SharedSequenceConvertibleType, R: ObserverType>(lhs: L, rhs: R) -> Disposable
      where L.SharingStrategy == L.DriverSharingStrategy, R.E == L.E {
    return lhs.drive(rhs)
  }

  static func *> <L: SharedSequenceConvertibleType, R: ObserverType>(lhs: L, rhs: R) -> Disposable
      where L.SharingStrategy == L.DriverSharingStrategy, R.E == L.E? {
    return lhs.drive(rhs)
  }

  static func *> <L: SharedSequenceConvertibleType>(lhs: L, rhs: Variable<L.E>) -> Disposable {
      where L.SharingStrategy == L.DriverSharingStrategy {
    return lhs.drive(rhs)
  }
}

演算子はお好きなものを

演算子はチームのみんなが読みやすく覚えやすいものを選べばいいです。今回思いつきで使った演算子は、

  • bindTo は F#, Elixir などのバイプライン演算子っぽい感じで
  • addDisposableTo は add が付いてるので + を使って表現
  • drive は * がハンドルとか船の舵を思わせるので

という理由で選んでみました。

k5n
好きなことはプログラミングと楽器演奏。そのうち金になる方を仕事に、ならないほうを趣味にしています。 若かりし頃はベンチャーでバリバリやってましたが、一度体を壊してからは忙し過ぎない仕事を探してマイペースにやってます。 C/C++, Swift, Objective-C, Java, Kotlin, Ruby, Python, PHP, TypeScript, Rust
creato
「言われたものを作るだけ」ではない共創型システムパートナーを掲げる名古屋の少数精鋭開発会社。
http://www.creato-c.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした