Elixirで整数リストが「謎の文字」として返ってくる現象と、文字列・文字リストについて

[__kokura.ex__ / __fukuoka.ex__](https://fukuokaex.connpass.com/) のim_miolabと申します。

さて、Elixirで整数の数値リストを取り扱っていると、__謎の文字__ が返ってくることがあります。

```iex:iex
iex()> list_int = [80, 90, 110]
'PZn'
```

この現象にからめて、以下つらつらと書いてみます。


# 先に結論とまとめ

- 整数リスト内の全要素が __ASCIIコードのエスケープシーケンスや印字可能文字の範囲内__ にあると、__文字リスト__(charlist)として出力される。

- Elixirでは、「文字列」と「文字リスト」は区別される。

|  | 文字の囲いかた | e.g. | 特徴 | データ型 |
|:-:|:--|:--|:--|:--|
| 文字列 | ダブルクオート（""） | "hoge" | バイナリ | BitString |
| 文字リスト | シングルクオート（''）| 'hoge' | 整数のリスト | List |

- 関数によって、<br>
  ・文字列を引数に期待するもの<br>
  ・List（文字リスト）を引数に期待するもの<br>
  とがあるので、使い分けが必要。


# ざっくり検証

それでは、具体的にみていきます。

## 実行環境

- Elixir ver 1.9.4
- Windows 10 Pro
- Bash

## リスト（List）

Elixirのリスト(List) は、`[ 要素, 要素, ... ]` のように書きます。
（異なる型の要素も同居できます）

以下、`iex`で整数のリストを書いてみました。

```iex:iex
iex()> list_a = [80, 1, 1100]
[80, 1, 1100]
```

いちおう、型も見てみます。

```iex:iex
# 「list_a」の型を確認
iex()> i list_a
Term
  [80, 1, 1100]
Data type
  List
.
.
.

# 「list_a の最初の要素」の型を確認
iex()> i list_a |> Enum.at(0)
Term
  80
Data type
  Integer
.
.
.
```

リストの中身は、ちゃんとInteger（整数）型になっています。


## 整数リストの全要素が、ASCIIコードの印字可能範囲にあるケース

冒頭でふれた現象についてです。

```iex:iex
iex()> list_int = [80, 90, 110]
'PZn'
```

「先に結論とまとめ」でも書いたとおり、リスト内の要素がASCIIコード10進数の印字可能範囲に全ヒットしたため、

80 → P
90 → Z
110 → n

となって、`'PZn'`として出力されています。

型を見てみると、以下のとおりです。

```iex:iex
# 「list_int」の型を確認
iex()> i list_int
Term
  'PZn'
Data type
  List
.
.
.

# 「list_int の最初の要素」の型を確認
iex()> i list_int |> Enum.at(0)
Term
  80
Data type
  Integer
.
.
.
```

狐につままれたような感がしなくもないですが、中の要素はIntegerだということです。

念のため、もう少し確認してみます。

```iex:iex
iex()> list_int
'PZn'

iex()> list_int |> Enum.sum
280
```

リスト内の要素は文字でなく、数値ですね。
（∵ 80 + 90 + 110 = 280）


## iex以外でもやってみる

`iex`以外でも一応確認してみます。


### スクリプト実行

おためしで、簡単なコードを書いてみました。

```Elixir:charlist.exs
defmodule Charlist do
  def print_list do
    list_int = [80, 90, 110]
    list_int |> IO.puts
    Enum.sum(list_int) |> IO.puts
  end
end

Charlist.print_list
```

こちらをスクリプト実行してみます。

```bash:bash
$ elixir charlist.exs
PZn
280
```

iexと同様、ASCIIコード文字列でいったん出力されています。
リストで中身はIntegerなので、`Enum.sum`はきちんと機能しています。


### コンパイル実行

コンパイル実行だとどうなるのでしょうか。

さっきと同じソースコードを、こんどは`ex`ファイルとして用意します。

```Elixir:charlist.ex
defmodule Charlist do
  def print_list do
    list_int = [80, 90, 110]
    list_int |> IO.puts
    Enum.sum(list_int) |> IO.puts
  end
end

Charlist.print_list
```

`elixirc`でコンパイル実行してみます。

```bash:bash
$ elixirc charlist.ex
PZn
280
```

こちらも同様の出力結果となっています。

つまり、この現象は`iex`特有のものではない、ということです。


## コードポイントを指定して文字のリストを返す

逆に、これを利用して、ASCIIコード10進数の整数リストを「文字のリスト」として返すことが可能です。

```iex:iex
iex()> list_im = [105, 109]
'im'
```

`'im'`の出力結果が得られました！
（ASCIIコード10進数で、105が`i`、109が`m`に該当）


# 文字列と文字リスト

「文字のリスト」について、ここで一度整理します。

まず、文字リスト（charlist）と文字列は別のものです。
たとえば、以下の2つは似ていますが異なるものです。

- "im"
- 'im'

```iex:iex
iex()> "im" == 'im'
false
```

ダブルクオート「`""`」で囲まれた文字は __文字列__、
シングルクオート「`''`」で囲まれた文字は __文字リスト__ になります。

__文字列__ は、UTF-8でエンコードされたバイナリです。
（いわゆる他の言語でいう文字列に近いのはこっち）

__文字リスト__ は、文字のリストとは言うものの、じつは整数のリストです。
さきほどの`list_im = [105, 109]`（→結果は`'im'`）を見てみると。。。

```iex:iex
iex()> i list_im
Term
  'im'
Data type
  List
Description
  This is a list of integers that is printed as a sequence of characters
  delimited by single quotes because all the integers in it represent printable
  ASCII characters. Conventionally, a list of Unicode code points is known as a
  charlist and a list of ASCII characters is a subset of it.
Raw representation
  [105, 109]
.
.
.
```

`Description`に、書かれていますね。
__This is a list of integers ...__ であると。
（いっしょにASCIIコードの印字範囲うんぬんの話も説明されてる）

文字リストは整数のリストであるので、以下のようなことができます。

```iex:iex
iex()> 'im' |> Enum.sum
214

# 105 + 109 = 214
```

反対に、文字列を引数として使う関数では、文字リストは使えません（リストなので）。

```iex:iex
# これはOK
iex()> "im" |> String.reverse
"mi"

# これはNG
iex()> 'im' |> String.reverse
** (FunctionClauseError) no function clause matching in String.Unicode.next_grapheme_size/1
.
.
.
```

つまり、

- 文字列を引数として期待する関数
- リスト（文字リスト）を引数として期待する関数

の使い分けが必要、ということになります。


# 文字リストが準備されてる目的は？（結論にかえて）

結局、「何が目的で`[80, 90, 110]`が`'PZn'`として出力されるのか？」
ってことになるんですが、

一部のErlangモジュールに文字情報を渡すとき、バイナリの文字列でなくて文字リスト（整数のリスト）として持たせておく必要があるので、その名残(？)が由来なのかなあと推測しています。

>文字リストがサポートされているのは一部のErlangモジュールがそれを必要としているからです。
（出典：[Elixir School](https://elixirschool.com/ja/lessons/basics/strings/)）

これ以上のはっきりした由来はよく分かりませんでしたmm

なにかわかったら追記します！

p.s.
@お詳しい方　ぜひそっと教えてくださいませ。

---

### 参考文献
- 『プログラミングElixir』
- [Elixir公式 (JP)「バイナリ、文字列、文字リスト」](https://elixir-lang.jp/getting-started/binaries-strings-and-char-lists.html#%E6%96%87%E5%AD%97%E3%83%AA%E3%82%B9%E3%83%88)
- [Elixir School 「文字列」](https://elixirschool.com/ja/lessons/basics/strings/)
- [Wikipedia (ASCII)](https://ja.wikipedia.org/wiki/ASCII)