定食屋おろポン

おろしポン酢と青ネギはかけ放題です

Swiftのmapは処理だけしてVoid返すことは出来ないから気ィ付けや

追記

この記事の情報は古いので注意して下さい。
最新のXcodeでは、mapに返り値を持たないクロージャを渡しても問題ありません。

追記ここまで

SwiftのArrayは、非破壊的なメソッドmapを持っています。素晴らしい。 enumerateObjectsUsingBlock:とか書いてられませんよホントに。メソッド名が長いのはともかくBlocksの記法がfuckingblocksyntaxでうんざりします。

mapの宣言はfunc map<U>(transform: (T) -> U) -> Array<U>となっています。 Haskellでいうところのmap :: (t -> u) -> [t] -> [u]ですね。

T型の要素を持つArrayと、T型を引数に取ってU型を返すクロージャがあります。T型のArrayの全ての要素にクロージャを適用し、U型のArrayを返すのがmapです。


まずはIntのArrayをmapしてIntのArrayを返すコード

let numbers :Array<Int> = [1,2,3,4] // Int型のArray

let doubled :Array<Int> = numbers.map { (n :Int) -> Int in
    return n * 2 // 全ての要素を2倍する
}

println(doubled) //=> [2, 4, 6, 8]

次に、IntのArrayをmapしてStringのArrayを返すコード

let numbers :Array<Int> = [1,2,3,4] // Int型のArray

let repeated :Array<String> = numbers.map { (n :Int) -> String in
    var retVal :String = ""
    for _ in 0..n { retVal += "★" } // ★をn回繰り返す
    return retVal
}

println(repeated) //=> [★, ★★, ★★★, ★★★★]

最後に、IntのArrayをmapするが、処理だけして値は返さない--Voidを返す--コード

let numbers :Array<Int> = [1,2,3,4] // Int型のArray

numbers.map { (n :Int) -> Void in // ここでランタイムエラー
    println(n)
}

死にます。ちなみにビルドは通り、実行時に死にます。


Rubyなんかでは、こういうmapは普通に書けます。

numbers = [1,2,3,4]
numbers.map { |n| print n } #=> 1234

考察

まず、前提として「Void型を要素にもつArray」は宣言できます。つまり、コンパイルが通ります。

var voids :Void[]
var voids2 :()[]

これに、実際にVoidのArrayを代入すると、コンパイルは通りますが、RuntimeErrorで死にます。

var voids :Void[]

voids = [()] // ここで死ぬ

空のArrayですら代入できません。コンパイルは通りますが、同様にRuntimeErrorで死にます。

var voids :Void[] = [] // ここで死ぬ

Swiftmapは非破壊的なので、map自体は(再)代入を行いません。 そのため、このコードでは代入は行われませんが、やはり死んでしまいます。

let numbers :Array<Int> = [1,2,3,4] // Int型のArray

numbers.map { (n :Int) -> Void in // ここでランタイムエラー
    println(n)
}

あくまで推測ですが、mapを走らせる際に、内部的にはArray<Void>インスタンスを作って処理結果を随時代入しようとしていて、その際に死ぬのでしょう。

Swiftのmapは処理だけしてVoid返すことは出来ない」と同時に、そもそもArray<Void>は使ってはいけない、と考えてよいと思います。

なお、配列に対して副作用だけ起こしてなにも値を返したくない際は、大人しくfor inを使えばよいです。

let numbers :Array<Int> = [1,2,3,4] // Int型のArray

for n :Int in numbers {
    println(n)
}

// 出力
1
2
3
4

追記

Twitter等で「Voidを返す」って表現がアレ、との指摘があります。 たしかにアレなんですが、ドキュメントに「値を返さないってのはな、実は()を返してるんだ」とあるので見逃して下さい。

https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html

Strictly speaking, the sayGoodbye function does still return a value, even though no return value is defined. Functions without a defined return type return a special value of type Void. This is simply an empty tuple, in effect a tuple with zero elements, which can be written as ().