November 24, 2016

【Swift】OptionalにFunctionalっぽい感じのextensionを生やす

Categories: 未分類 | Tags: #Swift #Functional #Tips


SwiftのOptionalにちょっとした関数を定義して、ちょびっとFunctionalに扱えるようにしてみます。
※ちなみにモナドとかとはまた別のお話です。

if let を関数で

通常,nilかどうかチェックしてunwrapする場合にif letを使うのですが、
これを関数で行えるようにしてみます。

let num: Int? = 100
if let num = num {
    print("output:", num)
}

do の定義

こんな感じで do 関数を定義します。

extension Optional {
    @discardableResult
    func `do`(block: (Wrapped) -> Void) -> Optional<Wrapped> {
        if case .some(let wrapped) = self {
            block(wrapped)
        }
        return self
    }    
}

値がある場合には、渡されたclosureに対してunwrapした値を渡します。そして、この関数の返り値として、自分自身を渡してあげます。

使ってみる

if letの代わりとして使うこともできるし、関数の戻り値としてOptional自身をそのまま返すので、他の関数に繋ぐこともできます。

let num: Int? = 100
num.do { print("output:", $0) }

// if letで行っていた出力をしつつ、mapでStringに変換する
let numString = num.do { print("output:", $0) }.map { "\($0)" }

??を関数で

nil-coalescing operator (??)も関数化してみます。

let array: [Int] = []
// 配列を最初があればそれを、なければ(=nil)デフォルトとして0を代入する
let num: Int = array.first ?? 0

こういうやつですね。

関数を定義する

extension Optional {
    func get(or `default`: @autoclosure () -> Wrapped) -> Wrapped {
        if case .some(let wrapped) = self {
            return wrapped
        }
        return `default`()
    }
}

ポイントとしてはunwrap出来た場合はその値を、そうでなければ引数で渡した値を返却するようにしています。 また、引数に対して @autoclosure をかけてあげると、
unwrap出来た場合に引数で渡した部分が評価されないので、若干やさしいです。
というよりも、 ?? 自体が @autoclosure かかっているので、それに合わせた方が良さそうです。

使ってみる

すごい適当な例ですが。

let username: String? = user.name
// 通常のOptional Biding
let uppercasedName = (username ?? getDefaultName()).uppercased()
// get(or:)を使ってみる。
// 2を採用した場合、unwrap出来た場合は `getDefaultName()` が評価されずに済む
let uppercasedName = username.get(or: getDefaultName()).uppercased()
let greeting = "Hello, \(uppercasedName))"

let array: [Int] = []
let num: Int = array.first.get(or: 0)

あまり大差ないですが、 ?? だと (optional ?? defaultValue) みたいに括弧で囲まないと次の関数に繋げられないので
ちょっとスッキリしますね。

まとめ

そんなに頻繁に使うものじゃないけど、普段使っているものも別の形で書けるし、ちょびっとFunctionalに扱えるようになるよ、という紹介でした。


written by sgr-ksmt