じょ
何言ってるんだ?というタイトルですがこういうことです。
let a = 0
ifTrue(a == 0) { print("0") }
.elseIf(a == 1) { print("1") }
.else { print("other") }
先日メソッドチェーン原理主義という言葉を思いつきまして、メソッドチェーン原理主義者であれば制御構文もメソッドチェーンでなければ許されないのではないかと考え作ってみました。
賢明な皆さまはもうお気づきだと思いますが、このエントリは「特に何の意味もない」エントリです。戯れ言には付き合わないタイプの方はすぐさまお引き取り頂ければ幸いです。
なお、以下のコードはすべてSwift5.1で書かれています。
最初の実装
糊
メソッドチェーンを行うためにはチェーンを繋ぐ「糊」に当たるものが必要です。今回はこんな感じです。
enum IfResult {
case `true`, `false`
}
もう既にバカっぽさ全開です。
メソッド群は条件が真のときはIfResult.true
を偽の時はIfResult.false
を戻すように作成します。
入口
IfResult
を使って早速メソッドチェーンの起点となる関数を作成します。
private func doAndTrue(_ f: () -> Void) -> IfResult {
(f() is Void) ? .true : .false
}
func ifTrue(_ condition: @autoclosure () -> Bool, _ excute: () -> Void) -> IfResult {
condition() ? doAndTrue(excute) : .false
}
頭のおかしすぎる関数が混ざっていますが、ifTrue(_:_:)
自体は難しくはないですね。
当然のようにdoAndTrue(_:)
関数で警告が出ていますが無視しましょう!(後で消えてなくなるのでご安心ください)
繋げる
起点はできましたのでIfResult
を拡張してメソッドチェーンが作れるようにしていきます。
enum IfResult {
case `true`, `false`
@discardableResult
func elseIf(_ condition: @autoclosure () -> Bool, _ excute: () -> Void) -> Self {
self == .false && condition() ? doAndTrue(excute) : self
}
}
IfResult
を戻したメソッドの条件が真であった場合は以降の処理はすべてキャンセルされます。
ですので、IfResult
がfalse
の時だけ条件を検査し処理するようにします。
さらにもう一つ
enum IfResult {
case `true`, `false`
func `else`(_ excute: () -> Void) {
self == .false ? excute() : ()
}
}
これは簡単ですね。
できた
では冒頭で提示したメソッドチェーンを実行してみましょう
func f(_ a: Int) {
ifTrue(a == 0) { print("0") }
.elseIf(a == 1) { print("1") }
.else { print("other") }
}
(0...3).forEach(f)
// prints ...
// 0
// 1
// other
// other
ちゃんと動いてますね。
改良された実装
ちゃんと動いてはいますが本当にこれでいいでしょうか?
else(_:)
メソッドが何も戻さないためメソッドチェーンはここで途切れることになります。
次につながるようになっていた方がよさそうです。では次に何を渡せばいいでしょう。
やはり何らかの値を伝播したいですね。
例えば
let a: String = ifTrue(cond) { "なにか" }.else { "なんだ?" }
のような感じです。
メソッドチェーンを次につなげるために先の実装を改良していきましょう。
糊
enum IfResult<Value> {
case `false`
case value(Value)
struct IfResultError: Error {}
func get() throws -> Value {
guard case let .value(v) = self else { throw IfResultError() }
return v
}
}
値を持てるようにしました。
またget()
メソッドで値そのものを取り出せるようにしました。
入口
ifTrue(_:_:)
メソッドを改修します。
func ifTrue<T>(_ condition: @autoclosure () -> Bool, _ excute: () -> T) -> IfResult<T> {
condition() ? .value(excute()) : .false
}
不格好なdoAndTrue(_:)
が不要になりすっきりしました。
繋げる
elseIf(_:_:)
とelse(_:)
もサクッと実装します。
再掲になる部分は除いています
enum IfResult<Value> {
case `false`
case value(Value)
@discardableResult
func elseIf(_ condition: @autoclosure () -> Bool, _ excute: () -> Value) -> Self {
switch self {
case .false: return condition() ? .value(excute()) : .false
case .value: return self
}
}
@discardableResult
func `else`( _ excute: () -> Value) -> Self {
switch self {
case .false: return .value(excute())
case .value: return self
}
}
}
ちょっと長くなってしまいましたがごく素直な実装です。
できた
新しいメソッドチェーンを見てみましょう。
func f(_ a: Int) -> IfResult<String> {
ifTrue(a == 0) { "0" }
.elseIf(a == 1) { "1" }
.else { "other" }
}
(0...3)
.map(f)
.map { r in Result { try r.get() } }
.forEach { _ = $0.map { print($0) } }
// prints ...
// 0
// 1
// other
// other
問題なく動いています。
完成です。
拡張された実装
ホントに完成?
値を取り出すのにResult
に頼っています。これはちょっとどうかと思います。
なので実装を拡張します。
もなど
extension IfResult {
@discardableResult
func map<T>(_ transform: (Value) -> T) -> IfResult<T> {
if case let .value(v) = self { return .value(transform(v)) }
return .false
}
@discardableResult
func flatMap<T>(_ transform: (Value) -> IfResult<T>) -> IfResult<T> {
if case let .value(v) = self { return transform(v) }
return .false
}
}
利用例
func fizzBuzz1(_ i: Int) -> IfResult<String> {
ifTrue(i < 1) { fatalError("あかんて") }
.elseIf(i.isMultiple(of: 3) && i.isMultiple(of: 5)) { "FizzBuzz" }
.elseIf(i.isMultiple(of: 3)) { "Fizz" }
.elseIf(i.isMultiple(of: 5)) { "Buzz" }
.else { String(i) }
}
(1...100)
.map(fizzBuzz1)
.forEach { $0.map { print($0) } }
_人人人人人人人人人_
> 突然のFizzBuzz <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄
比較
ついでなのでEquatable
にしておきましょう。
Value
の型がEquatable
でないと比較することができないので制約付きのextension
になります。
extension IfResult: Equatable where Value: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.false, .false): return true
case let (.value(v1), .value(v2)): return v1 == v2
default: return false
}
}
}
まとめ
突然の思い付きにしてはよくできたのではないでしょうか?
関数は可能な限り1つのメソッドチェーンで完結するように書いています。
いくつか難しいものがあり妥協がたくさんありました。
真のメソッドチェーン原理主義者様ならそれらもメソッドチェーンで書くのでしょう。
告白
最後に真実を告白させていただきます。
実は私はメソッドチェーン原理主義者ではありません。
ですのでこのエントリで作成したメソッド群は使うことはないでしょう。
使う人がいるのかも疑問です。
ちゃんと最初に「特に何の意味もない」戯れ言だって断ってるからね?
怒っちゃだめよ。