Ruby と Scala と Swift と JavaScript における map と flatMap 



Ruby, Scala, Swift, JavaScript での List や Array といったデータ型の map と flatMap について`[1, 2, 3, [4, [5, [6]]]]`という何気ないデータを使って動きを見てみます。

## map

mapの動作イメージは、箱から出した値を変換する関数に渡して変換し、その結果をまた箱に詰める感じだと思っています。

![map.png](https://qiita-image-store.s3.amazonaws.com/0/76146/8f074316-9b37-0c61-0146-fe3dfd52e5a3.png "map.png")

元のデータがリストの場合は、すべての要素が関数に渡され、それぞれ変換された値が入った新しい箱ができます。

### Ruby

```Ruby
[1] pry(main)> [1, 2, 3, [4, [5, [6]]]].map {|x| p x; x }
1                              # 1つ目の要素
2                              # 2つめの要素
3                              # 3つめの要素
[4, [5, [6]]]                  # 4つめの要素
=> [1, 2, 3, [4, [5, [6]]]]    # 新しいリスト
```

### Scala

```Scala
scala> List(1, 2, 3, List(4, List(5, List(6)))).map { (x) => println(x); x }
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
List(4, List(5, List(6)))      # 4つ目の要素
res1: List[Any] = List(1, 2, 3, List(4, List(5, List(6))))    # 新しいリスト
```

### Swift

```Swift
println(([1, 2, 3, [4, [5, [6]]]] as [AnyObject]).map { i -> AnyObject in println(i); return i; })
```

```
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
(                              # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  | 4つ目の要素
            6                  #  |
        )                      #  |
    )                          #  |
)                              # ---
[1, 2, 3, (                    # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  | 新しいリスト
            6                  #  |
        )                      #  |
    )                          #  |
)]                             # ---
```

### JavaScript

```JavaScript
> [1, 2, 3, [4, [5, [6]]]].map(function(x){ console.log(x); return x; })
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
[ 4, [ 5, [ 6 ] ] ]            # 4つ目の要素
[ 1,                           # ---
  2,                           #  | 新しいリスト
  3,                           #  |
  [ 4, [ 5, [Object] ] ] ]     # ---
```

### まとめ

どの言語も全部同じ挙動。


## flatMap

flatMapはmapしてflattenする、ただのflattenとmapの合成関数だと思っています。
つまりこういうものなんじゃないかと→`flatMap(f) = flatten(map(f))`。

![flatmap1.png](https://qiita-image-store.s3.amazonaws.com/0/76146/f5f11d9e-d9f3-927e-44d7-0ca8a1dda205.png "flatmap1.png")

こうやって書くとmapと同じに見えますが、大きな違いは「変換！」の関数の戻り値で、mapの場合は値を返しますが、flatMapの場合は箱を返す必要があります。
せっかく合成した関数ですが、わかりやすいようにバラバラにするとこんな感じになります。

![flatmap2.png](https://qiita-image-store.s3.amazonaws.com/0/76146/3ecbd2be-3c81-497f-32eb-7da6c4aae9f7.png "flatmap2.png")

これだけだとflatMapの存在意義が感じられないのですが、箱で返すので実際にはいろいろ利用用途があります。値をいじるだけならmapでできますが、要素を増やしたり減らしたりするときはflatMapを使います。
例えば`[値] => [値, -値]`という変換ができます。

![flatmap3.png](https://qiita-image-store.s3.amazonaws.com/0/76146/b06c2d11-eedd-63d3-e961-5fc5cd6e5d5e.png "flatmap3.png")


### Ruby

```Ruby
[1] pry(main)> [1, 2, 3, [4, [5, [6]]]].flat_map {|x| p x; [x, x] }
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
[4, [5, [6]]]                  # 4つ目の要素
=> [1, 1, 2, 2, 3, 3, [4, [5, [6]]], [4, [5, [6]]]]    # 新しいリスト
```

mapしてflattenすると、

```Ruby
[2] pry(main)> [1, 2, 3, [4, [5, [6]]]].map {|x| p x; [x, x] }.flatten
1
2
3
[4, [5, [6]]]
=> [1, 1, 2, 2, 3, 3, 4, 5, 6, 4, 5, 6]
```

あれ？ 同じにならない。
実はflattenは引数を取るメソッドで、何段階flatにするのか指定できます。
指定しないと完全にflatにするみたいです。

```Ruby
[3] pry(main)> [1, 2, 3, [4, [5, [6]]]].map {|x| p x; [x, x] }.flatten(1)
1
2
3
[4, [5, [6]]]
=> [1, 1, 2, 2, 3, 3, [4, [5, [6]]], [4, [5, [6]]]]
```

１階層だけflatにしたら同じ結果になりました。

### Scala

```Scala
scala> List(1, 2, 3, List(4, List(5, List(6)))).flatMap { (x) => println(x); List(x, x) }
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
List(4, List(5, List(6)))      # 4つ目の要素
res2: List[Any] = List(1, 1, 2, 2, 3, 3, List(4, List(5, List(6))), List(4, List(5, List(6))))     # 新しいリスト
```

mapしてflattenすると、

```Scala
scala> List(1, 2, 3, List(4, List(5, List(6)))).map{ (x) => println(x); List(x, x) }.flatten
1
2
3
List(4, List(5, List(6)))
res1: List[Any] = List(1, 1, 2, 2, 3, 3, List(4, List(5, List(6))), List(4, List(5, List(6))))
```

同じになりました。

### Swift

```Swift
println(([1, 2, 3, [4, [5, [6]]]] as [AnyObject]).flatMap { i -> [AnyObject] in println(i); return [i, i]; })
```

```
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
(                              # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  | 4つ目の要素
            6                  #  |
        )                      #  |
    )                          #  |
)                              # ---
[1, 1, 2, 2, 3, 3, (           # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  |
            6                  #  |
        )                      #  |
    )                          #  |
), (                           #  | 新しいリスト
    4,                         #  |   
        (                      #  |
        5,                     #  |
                (              #  |
            6                  #  |
        )                      #  |
    )                          #  |
)]                             # ---
```

mapしてflattenしようとしたら、flattenはありませんでした。
しょうがないのでflattenの代わりにflatMapを使って、mapで作った箱をそのまま返す感じで。

```Swift
println(([1, 2, 3, [4, [5, [6]]]] as [AnyObject]).map { i -> [AnyObject] in println(i); return [i, i]; }.flatMap { $0 })
```

```
1
2
3
(
    4,
        (
        5,
                (
            6
        )
    )
)
[1, 1, 2, 2, 3, 3, (
    4,
        (
        5,
                (
            6
        )
    )
), (
    4,
        (
        5,
                (
            6
        )
    )
)]
```

同じ結果になりました。

### JavaScript

```JavaScript
> [1, 2, 3, [4, [5, [6]]]].flatMap(function(x){ console.log(x); return [x, x]; })
TypeError: Object 1,2,3,4,5,6 has no method 'flatMap'
    at repl:1:27
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.emit (events.js:95:17)
    at Interface._onLine (readline.js:203:10)
    at Interface._line (readline.js:532:8)
    at Interface._ttyWrite (readline.js:761:14)
    at ReadStream.onkeypress (readline.js:100:10)
    at ReadStream.emit (events.js:98:17)
    at emitKey (readline.js:1096:12)
```

```JavaScript
> [1, 2, 3, [4, [5, [6]]]].flatten
undefined    
```

そもそも無かった。

### まとめ

- 普通の使い方ならどの言語も全部同じ挙動。当たり前か、、、
- JavaScriptにはない。