Optionalとmapとfunction

Sunday, March 6, 2016

1日1記事とかに拘らず、なるべく1日1記事は最低書くとして、他書きたい内容ができてきたら追加で記事を書いている、そんな感じです。
今日は最近自分がtry! Swiftのプレゼンを見て、気づいたことがあったのでそれを書き留めておこうかなと。

今回は(個人的には結構)実践的というか、それぞれを理解していれば、「こういう書き方ができるんだなあ」というものです。

Optional型をunwrapして使う場合


たとえば、

func addGreeting(name: String) -> String {
    return "Hello, \(name)"
}

と、Stringの型を取る関数があったとします。これは単純に、nameを与えて、“Hello, “という文字列を付与して返して、挨拶文にするって感じです。 ここで、

let name: String = "Taro"
let greeting = addGreeting(name)
print(greeting) // "Hello, Taro"

この場合は特に何も問題ありません。
しかし、名前があったら挨拶をするという例で、String?型にする場合は、

let name: String? = "Taro"

if let name = name {
    let greeting = addGreeting(name)
    print(greeting) // "Hello, Taro"
}

どうしてもif let でunwrapしてからでないと、addGreetingに渡すことができません。

Optional型の関数mapを使う


そこで、if letを使わず、addGreetingを適応するのを、Optionalに実装されている map 関数を使って実現してみます。
map というと、コレクションに対して使う場合がほとんどなので、Optional型に使えるの?って思いがちですが、実は使えるんです。
実際に、apple/swiftのOptional.swiftをのぞくと、このように実装されています。

@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U? {
    switch self {
    case .Some(let y):
        return .Some(try f(y))
    case .None:
        return .None
    }
}

つまり、

  • Someの場合は、unwrapして、クロージャを適応して値をOptionalで返す
  • None(nil)の場合は、そのままNone(nil)を返す

といったことをしてくれます。

これを使うと、こんな感じに、if let を書かずとも、変数に適応したい処理を適応することができます。
beforeとafterを載せておきます。

// before
var name: String? = "Hanako"
var nickName: String?

if let name = name {
    nickName = name + "-chan"
}
print(nickName)

// after
var name: String? = "Hanako"
var nickName = name.map { $0 + "-chan" }
print(nickName)

名前があったら、“ちゃん"を付けたニックネームを作るという例ですが、このように、if letを取り除くことができました。

更に、swiftの関数とは結局、

Swiftにおいて、関数はファーストクラス、つまり関数は第一級関数であることはすでに皆さんご存知のとおりです。つまり、…

ということで、関数もクロージャと見なせる(厳密には解釈が少し異なりますが、)ので、
最初に出したgreetingの例は、

//before
var name: String? = "Taro"

if let name = name {
    let greeting = addGreeting(name)
    print(greeting) // "Hello, Taro"
}

// after
var name: String? = "Taro"
print(name.map(addGreeting) ?? "")

といった感じでまとめられます。
上記2つの例だと結果がOptionalになるので、printするときとかは"Optinal(“~~~”)“となってしまいますが、例えば、
あるクラスのString?型のtextプロパティに、値が代入された時に、nilでなければ、文字をlowerCaseにしたいという場合は、

struct NoteA {
    var text: String? {
        get {
            return self.text
        }
        set {
            if let newValue = newValue {
                text = newValue.lowercaseString
            } else {
                text = newValue
            }
        }
    }
}

struct NoteB {
    var text: String? {
        get {
            return self.text
        }
        set {
            text = newValue.map { $0.lowercaseString }
        }
    }
}

こんな感じで2パターンの書き方ができますが、 後者(NoteB) の方がスッキリかけます!


場所によってはすごく使い勝手が良いので、うまく使ってみてください!

techSwiftTips

class protocolってなんぞ

Blending Cultureのサンプルを拡張してみた