yashigani?.days

週刊少年ジャンプについてだらだら書きます

Optinalのmapがちょっと使いにくいから拡張してやった #swift

SwiftOptionalにはmapがある.

func map<U>(f: T -> U) -> U?

引数のfT(Optionalで包まれいる値)を受け取ってUを返す関数を受け取って,fの返り値であるUOptionalで包んだU?を返す関数です. 要は,if-letを使って明示的にunwrapしなくてもOptionalの構造を保ったまま中の値に関数を適用するための関数ということですね.

このmapですが,Optionalな値を返す関数と使ってみると案外使いにくいことがわかります.

let URL: NSURL? = NSURL(string: "http://yashigani.hatenablog.com")
if let URLString = URL.map({ $0.absoluteString }) {  // URLStringはString型を期待
    println(URLString)  // => Optional("http://yashigani.hatenablog.com")
}

Optionalを返す関数を適用する場合,mapの返り値は二重にOptionalに包まれて返ってきてしまうのでif-letでunwrapしてもOptionalが残ってしまいます. つまり,実装は以下のようになっていることが予想できます.

func map<U>(f: T -> U) -> U? {
    switch self {
    case .None: return nil
    case .Some(let x): return .Some(f(x))
    }
}

このままでは使いにくいです. ところで,普通の値をとって何かに包まれた値を返す関数を,同じ何かに包まれた値に適用するといえばなにか思い出さないでしょうか? そう,モナドですね. Optionalmapモナドにおけるバインドに似た動作をします. ということはモナドっぽく拡張すればOptionalを返す関数に対しても使いやすくできるのではないでしょうか. 以下のように拡張してみます.

extension Optional {
    func bind<U>(f: T -> U?) -> U? {
        switch self {
        case .None: return nil
        case .Some(let x): return f(x)
        }
    }
}

実行結果は以下のようになります.

if let URLString = URL.bind({ $0.absoluteString }) {
    println(URLString)  // => http://yashigani.hatenablog.com
}

if let URLString = URL.bind({ (x: NSURL?) -> String? in return nil }) {
    println(URLString) // URLStringはnilになるので実行されない
}

狙いどおりいいかんじの動作になりました. 同僚のid:cockscombがはまってて,こうやったらいいんじゃない?ってアドバイスしたらおもったより学びがあった. はてなでの生活は毎日が学びです. 圧倒的感謝です.

余談

Optionalの実装を見たらmapのコメント間違ってた.

    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    func map<U>(f: (T) -> U) -> U?

返ってくるのはf(self!)じゃなくてf(self!)?です.

今日のお得情報

Swiftは関数型パラダイムの影響を色濃く受けているプログラミング言語なので,少しだけでも関数型プログラミングのエッセンスを学んでおくととても役に立ちます. 簡単に学ぶには以下の書籍がオススメです.

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

読んだ人の感想もご紹介します.