Elixir の String.split がモヤモヤします

[[ruby]`"".split(",").length` は 0 で、[golang]`len(strings.Split("", ","))` は 1 の話](https://qiita.com/y-amadatsu/items/109ecee82c773454c16e) で、Elixirが「もうよくわからない」と書かれていたので、もうちょい調べてみました。

Elixr は、1.6.5 を使ってます。

## 文字列のsplit

```elixir
Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> String.split("a", ",")
["a"]
iex(2)> String.split("a", " ")
["a"]
iex(3)> String.split("a", "") 
["", "a", ""]
iex(4)> String.split("a")
["a"]
iex(5)>
```

`String.split("a", "")` が謎の動きですね。

```elixir
iex(5)> String.split("", ",")
[""]
iex(6)> String.split("", " ")
[""]
iex(7)> String.split("", "")
["", ""]
iex(8)> String.split("")
[]
iex(9)>
```

`String.split("", "")` と `String.split("")` が、謎の動きですね。

## 公式ドキュメントを見る
 
公式ドキュメントを見ると、[String.split/1](https://hexdocs.pm/elixir/String.html#split/1) と [String.split/3](https://hexdocs.pm/elixir/String.html#split/3) があります。String.split の引数が１つの場合と3つの場合に別れるようです。引数が２つの場合は、第３引数にデフォルト値の`[]`が指定されたものとして扱われます。

## String.split/1

> Divides a string into substrings at each Unicode whitespace occurrence with leading and trailing

とあるので、

```elixir 
String.split(x, " ") == String.split(x)
```

となって欲しいのですが、 実際には

```elixir
iex(21)> String.split("a", " ") == String.split("a")
true
iex(22)> String.split("", " ") == String.split("")
false
```

xが空文字のときは、成立しません。

うん？ `Unicode whitespace ???　もしやと思って試してみると

```elixir
iex(23)> String.split("a　b c") # a と ｂ の間は全角スペースでbとcの間は半角スペース
["a", "b", "c"]
```

おおっ。思った以上にこれは複雑な感じですね。

## String.split/3

> Divides a string into substrings based on a pattern.

とあり、更に

> Empty strings are only removed from the result if the :trim option is set to true.

なんてことが書かれてます。

Exampleのところを見ると空文字で分割する例がありました。

> Splitting on empty string returns graphemes:

と書かれてます。実際に手元で試してみます。

```elixir
iex(26)> String.split("a", "")
["", "a", ""]
iex(27)> String.split("a", "", trim: true)
["a"]
iex(28)> String.graphemes("a")
["a"]
iex(29)> String.split("", "")
["", ""]
iex(30)> String.split("", "", trim: true)
[]
iex(31)> String.graphemes("")
[]
```

第２引数を指定するときは `trim: true` をつければ良いということ？

```elixir
iex(32)> String.split("a", ",", trim: true)
["a"]
iex(33)> String.split("a", " ", trim: true)
["a"]
iex(34)> String.split("a", "", trim: true)
["a"]
iex(35)> String.split("a")
["a"]
```

おっ。全部 `["a"]` になった。なんかスッキリ。

```elixir
iex(43)> String.split("", ",", trim: true)
[]
iex(44)> String.split("", " ", trim: true)
[]
iex(45)> String.split("", "", trim: true)
[]
iex(46)> String.split("")
[]
```

おっ。全部 `[]` になった。なんかスッキリ...。
いやいや、`trim: true` ってつけないといけないって時点でモヤモヤする。第３引数を省略したときのデフォルトが `trim: true` ってなってた方が良かったんじゃないかと思います。
　
## String.split のソースを見る

ちょっとだけソースを覗いてみたところ、String.split/3 の第２引数が `""` で、第3引数を省略した場合、意図的に前後に `""` をつけているようです。

先頭に `""` をつけているところは、以下の `split` です。

```elixir:string.ex
  def split(string, "", options) when is_binary(string) do
    parts = Keyword.get(options, :parts, :infinity)
    index = parts_to_index(parts)
    trim = Keyword.get(options, :trim, false) # 省略時が true だったら...

    if trim == false and index != 1 do
      ["" | split_empty(string, trim, index - 1)] # ここで意図的に "" を先頭に追加している
    else
      split_empty(string, trim, index)
    end
  end 
```

一番最後に `""` をつけているところは、 `split_empty` です。

```elixir:string.ex
  defp split_empty("", true, 1), do: []
  defp split_empty(string, _, 1), do: [string]

  defp split_empty(string, trim, count) do
    case next_grapheme(string) do
      {h, t} -> [h | split_empty(t, trim, count - 1)]
      nil -> split_empty("", trim, 1)
    end
  end
```

next_grapheme で取り出す文字がなくなると

```elixir
split_empty("", trim, 1)
```

が実行されて、trim がデフォルトでは、 false なので、

```elixir
defp split_empty(string, _, 1), do: [string]
```

が実行されて、 `[""]` が最後にくっつくようになっています。

```elixir
String.split("", "")
```

の場合は、最初に `""` がついて `[""]` となって、取り出す文字が最初からないので、最後に `""` が追加されて、 `["", ""]` になります。

うーん。でも、なんで、こんな実装（仕様）になっているんでしょうか？

ちなみに

```elixir
String.split("", " ")
```

や

```elixir
String.split("", ",")
```

の場合は、

```elixir:string.ex
  def split(string, pattern, []) when is_tuple(pattern) or is_binary(string) do
    :binary.split(string, pattern, [:global])
  end
```

が実行されるので、 Erlang の世界に飛び込むことになり、ここで力尽きました。

```elixir
String.split("")
```

の場合は、

```elixr:string.ex
  defdelegate split(binary), to: String.Break
```

で、 `String.Break.split/1` が呼ばれるみたいですが、ここで力尽きました。

## まとめ

- Elixr の String.split は奥が深くて謎だらけ
- String.split を使うときには、 `trim: true` をつけておいた方が結果がスッキリするような気がしないでもない。
- 結局なんか、モヤモヤして謎は深まるばかり。