48643


[swift - 勝手にnilが無視される挙動を使って安全？ -
スタック・オーバーフロー](https://ja.stackoverflow.com/questions/48585/%e5%8b%9d%e6%89%8b%e3%81%abnil%e3%81%8c%e7%84%a1%e8%a6%96%e3%81%95%e3%82%8c%e3%82%8b%e6%8c%99%e5%8b%95%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%a6%e5%ae%89%e5%85%a8)  
では、`ディクショナリ`における`nilの削除挙動`の質問をしました。

同様のソースをここに再掲しますが、

[code]

    let hoge: String? = nil
    let foo: String? = "foo"
    
    var dict = [String: Any]()
    
    if let hoge = hoge {
        dict["hoge"] = hoge
    }
    
    if let foo = foo {
        dict["foo"] = foo
    }
    
    print(dict) // => ["foo": "foo"]
    
[/code]

やはり `if let` の連続が書きにくいし、読みにくい。  
なんとかして別の手段はないかと思い `map` を試しました。

[code]

    let hoge: String? = nil
    let foo: String? = "foo"
    var dict = [String: Any]()
    
    hoge.map{ dict["hoge"] = $0 }
    foo.map{ dict["foo"] = $0 }
    
[/code]

これで目的が達成できたように感じます... しかしここで疑問が...

  * 質問1. mapは返り値をなにかに代入する目的のもののはず、この使い方は正しいのか？
  * 質問2. なぜ `Result of call to 'map' is unused` の警告はでないのか？

蛇足ですが、質問2の警告がたまたまXcodeの調子で出ないだけかと思い、

[code]

    func bar() -> String? {
        return "f"
    }
    
[/code]

という関数を作ってみたところ添付画像のように警告は出ていました。  
[![画像の説明をここに入力](https://i.stack.imgur.com/KfTQm.png)](https://i.stack.imgur.com/KfTQm.png)

さらに蛇足ですが、なぜ `bar関数`の戻り値を`String?`とオプショナルにしたかというと、  
`map`の定義が下記のようになっていたので `-> U?` の部分に似せることができるかと考えたためです。  
`public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?`  
（ただし、私がクロージャとジェネリクスが混合して書かれた型の読み方に慣れておらず、なにか勘違いしている可能性があります...）


CC BY-SA 4.0
2018-09-22T09:24:37.543
48642
2018-09-22T12:54:23.130
2018-09-22T10:15:02.967
9008
9008
question
swift swift4
オプショナルのmapの返り値は代入しなくてもよい？


_**・質問1. mapは返り値をなにかに代入する目的のもののはず、この使い方は正しいのか？**_

「正しい」と言うのを前のご質問の回答後半で「安全じゃない」と言ったのと逆の意味、つまりSwiftの実装が変わったりしても、
**挙動が変わって困る心配はなさそう、と言う意味に捉えると「正しい」** と言えます。

[`Optional`型の`map`のドキュメント](https://developer.apple.com/documentation/swift/optional/1539476-map)には、以下のように明記されています。

> Evaluates the given closure when this `Optional` instance is not nil,  
>  passing the unwrapped value as a parameter.
>
> (拙訳、要らないような気もしますが、質問で英文を掲載すると即座に拒否られる日本語サイトですので…)  
>  `Optional`インスタンスが非`nil`の場合にクロージャを評価する。その際クロージャのパラメータとしてはアンラップされた値が渡される。

また、「mapは返り値をなにかに代入する目的」についてですが、「
**確かに目的外使用は無駄が出るかもしれないし、リーダビリティを損なう可能性もあるけど、確実に動くんだし、他に手段がなければありかも**
」くらいのところでしょうか。

(個人的なことを言うと、「自分ではやらないが、他人がこんなコードを書いているのをみたら、なかなか上手いことやってるな、なんてニヤリとする」と言ったニュアンスです。)

大昔のSwiftには配列(正確には`Sequence`)に`forEach`と言うメソッドがなかったので、代わりに`map`を使う人たちがいました。そっちなら、「自分ではやらないし、他人がそんなコードを書いているのをみたら、無駄な配列を作ってすぐ捨ててることになります、なんて注意する」と言ったところなんですが。

今回の`Optional`の`map`の場合、クロージャの中で副作用として出来た値を型だけ`Optional`型にラップして返しているだけなので、目くじら立てるほどの無駄じゃないと言えるでしょう。

* * *

_**・質問2. なぜ`Result of call to 'map' is unused` の警告はでないのか？**_

これについては、どんな時にどんな警告が出るかと言うのはちゃんとドキュメント化されていないので、「将来のバージョンでは警告が出るようになる」可能性がありますし、若干推測も入るんですが。

まず、このメソッドの宣言には警告を抑制する`@discardableResult`なんてアノテーションはついていないようです。実際、こんな使い方だと(少なくともXcode
10では)警告が出ます。

[code]

    func setHogeToDict(_ str: String) -> String {
        dict["hoge"] = str
        return str
    }
    hoge.map{ setHogeToDict($0) } //! Result of call to 'map' is unused
    
[/code]

と言うわけで、`Optional.map`に渡されるクロージャの戻り値が`String`型なら、ちゃんと警告は出ます。(この場合、`map`の戻り値は`String?`型になる。)

警告が出ない例`hoge.map{ dict["hoge"] = $0 }`では、(Swiftの代入文は値を返さないので)
**クロージャの戻り値が`Void`型なので、この場合には警告を出さないようにしている**のだと思われます。(この場合、`map`の戻り値は`Void?`型(別の書き方なら`()?`型)になる。)

明確に戻り値なしを意味する、`Void`型と違って`Void?`型の値は一応使う時があるんですが、まぁ、そんなことは滅多にないだろうと言うことでしょう。実際、`Void?`型に推論される変数を宣言しようとすると、警告が出ます。

[code]

    let result = hoge.map{ dict["hoge"] = $0 }
    //! constant 'result' inferred to have type '()?', which may be unexpected
    
[/code]

* * *

ところで、上に「リーダビリティを損なう可能性もある」なんてことを書きましたが、どうせリーダビリティの方はちょっと我慢するなら、`Dictionary`型にちょっとした`extension`を定義しちゃう(独自`extension`の多用はリーダビリティを損なうとして嫌がる人たちがいるんですが)なんて手もありそうです。

[code]

    extension Dictionary {
        subscript(ignoreNil key: Key) -> Value? {
            get {
                return self[key]
            }
            set {
                if let value = newValue {
                    self[key] = value
                }
            }
        }
    
[/code]

}

[code]

    let hoge: String? = nil
    var dict = [String: Any]()
    dict[ignoreNil: "hoge"] = hoge
    print(dict) //->[:]
    
[/code]

* * *

コメントに「`Void?` （のあまり使用場面が想像できてないですが）」というのをいただいたので補足。

`Void?`型は値として空タプル`()`または`nil`を格納できますから、わざわざ変数(定数含む)の宣言まではしないでしょうが、値を利用してこんな書き方はできます。

[code]

    let hoge: String? = nil
    var dict = [String: Any]()
    let result: Void? = hoge.map{ dict["hoge"] = $0 } //`Void?`を明記すると警告は出ない
    //hogeが非nilの場合`result`の値も非nil(空タプル)になる
    if result != nil {
        print("dictに値が追加されました")
    } else {
        print("dictは変更されていません") //->dictは変更されていません
    }
    
[/code]

滅多にどころか、「こんなことする奴ぁいねぇよ!」と言いたくなるコード(後で条件分けするんなら初めからif-
let使えば数段読みやすい)ですが、こんなこともやろうと思えば出来ますと言うことで。

「違和感」については、Optional
Chaining代わりに`map`を使うテクニックの一種なんで、多くの人に広まればそれほどでもなくなるかもしれません。(Optional
Chainingに普通に`?.`を使った式の型が`Void?`になる例はいっぱいあるんですが、多分あまり気にしてないですよね?)


CC BY-SA 4.0
2018-09-22T11:18:27.393
48643
2018-09-22T12:54:23.130
2020-06-17T08:14:45.997
-1
13972
48642
answer