Elixirのiexは、印字可能な数字のリストを文字リテラルとして解釈する

書籍「[プログラミングElixir](https://www.ohmsha.co.jp/book/9784274219153/)」の中に書かれていた練習問題を考えてみました。

## 問

問：なぜiexは`'cat'`を文字列として表示しているのに、`'dog'`を文字コードで表示しているのだろう？

```elixir

iex> ['cat' | 'dog']
['cat', 100, 111, 103]
```

## 回答

内部的には、`['cat' | 'dog']`は、「`[[99, 97, 116], 100, 111, 103]`」という「数値のリスト」と「数値」が混在したリストとして扱われています。

そして、「数値のリスト」と「数値」が混合したリストを扱った際に、iexが「数値のリスト」を「文字リテラル」として解釈するためです。

これらの動きには、以下の3つが関連しています。

1. 単一引用符（シングルクォート）で囲んだ場合、「**文字のリスト**」になるから。
2. ヘッドとテイルでは、ヘッドは**値**、テイルは**リスト**として扱われるから。
3. iexでは、**ヒューリスティック**で動作しているから。値として「数字のリスト」があるとき、そのリスト内の全ての値が「印字（表示）可能文字の**文字コードの値と同じ**」だと「文字リテラル」として表示する動きとなっている。


もう少しわかりやすくイメージできるのが以下のパターン。

```elixir

iex> ['cat'++[0]|'dog']
[[99, 97, 116, 0], 100, 111, 103]
```

ヘッドの値は、「**++[0]**」により「文字のリスト」に「印字できない文字コード」が追加されたことにより、「文字のリスト（文字リテラル）」ではなく「数値のリスト」となりました。

一方、以下のようにヘッド「**h**」とテイル「**t**」に割り当てたのち、hとtの値をそれぞれ確認してみると、以下のようになりました。

```elixir

iex> [h|t] = ['cat'++[0]|[100,111,103]]
[[99, 97, 116, 0], 100, 111, 103]
iex> h
[99, 97, 116, 0]
iex> t
'dog'
```

テイル「t」の値は、`[100,111,103]`の数値リストに設定されています。
ただし、数値のリスト内の値が全て「印字（表示）可能文字の**文字コードの値と同じ**」であるため、`'dog'`として表示されています。




関連事項の内容を、もう少し深掘りしてみます。

## 1. 単一引用符（シングルクォート）で囲んだ場合、「**文字のリスト**」になるから。

Elixirでは、**文字リテラル**は「二重引用符（ダブルクォート）」と「単一引用符（シングルクォート）」で囲んだ値として扱っています。

しかし、二重引用符で囲んだ場合と単一引用符で囲んだ場合とで、Elixir内部での扱いに違いがあります。

その違いは、簡単にいうと以下となります。

 * **二重引用符（ダブルクォート）**で囲んだ場合は「**文字列**」[^1]として扱われる
 * **単一引用符（シングルクォート）**で囲んだ場合は「**文字のリスト**」として扱われる

[^1]: 厳密には、二重引用符で括った場合はバイナリ（バイト列）となりますが、ここでの説明は省略します。

それぞれを「**ガード節**」で利用する型判定の「*is_bitstring*」（文字列判定）と「*is_list*」（リスト判定）を利用して確認してみます。

```elixir
iex> s1 = "hello world"
"hello world"
iex> is_bitstring(s1)
true
iex> is_list(s1)
false
```

```elixir
iex> s2 = 'hello world'
'hello world'
iex> is_bitstring(s2)
false
iex> is_list(s2)
true
```

このように、二重引用符で囲んだ場合は「文字列であるがリストではない」と、単一引用符で囲んだ場合は「文字列ではないが文字[^2]のリストではある」と、わかります。

[^2]: 正確には文字として表現可能な数値

なお、単一引用符で囲んだ場合は「文字のリスト」の扱いですので、**Enum**を利用して数値計算などができます。

```elixir
iex> s2
'hello world'
iex> s2 ++ [0]
[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0]

iex> sp2 = s2 |> Enum.map(fn x -> x + 1 end)
'ifmmp!xpsme'

iex> sp2 ++ [0]
[105, 102, 109, 109, 112, 33, 120, 112, 115, 109, 101, 0]
```

## 2. ヘッドとテイルでは、ヘッドは**値**、テイルは**リスト**として扱われるから。

Elixirのリストは、空でない場合には、*リストの先頭要素である「ヘッド」*と、*先頭要素を除いたリストを構成する要素である「テイル」*に分かれます。

具体的なイメージは以下。
リストが、先頭要素の「1」の値と、先頭要素を除いたリスト「2, 3, 4, 5, 6」にわかれています。

```elixir

iex> [h|t] = [1,2,3,4,5,6]
[1, 2, 3, 4, 5, 6]
iex> h
1
iex> t
[2, 3, 4, 5, 6]
```

リストの構成要素はどの型とでも組み合わせられます。
そして、色々な型が要素になったリストも、ヘッドとテイルで分けることができます。

```elixir

iex> [h|t] = [[1,2,3],"4,5,6",7,8,9,true]
[[1, 2, 3], "4,5,6", 7, 8, 9, true]
iex> h
[1, 2, 3]
iex> t
["4,5,6", 7, 8, 9, true]

iex> [h2|t2] = t
["4,5,6", 7, 8, 9, true]
iex> h2
"4,5,6"
iex> t2
[7, 8, 9, true]
```

## 3. iexでは、**ヒューリスティック**で動作しているから。

Wikipediaによると、**ヒューリスティック**とは『*必ず正しい答えを導けるわけではないが、ある程度のレベルで正解に近い解を得ることができる方法*』とあります。

そして、冒頭でも触れましたが、iexは「値として*数字のリスト*があるとき、そのリスト内の全ての値が「**印字（表示）可能文字の文字コードの値と同じ**」だと「文字リテラル」として表示する動き」をとります。

これは、以下のように確認できます。

```elixir
iex> [1,2,3,4,5]
[1, 2, 3, 4, 5]
iex> [100,101,102]
'def'
iex> [1,2,3,[100,101,102],4,5]
[1, 2, 3, 'def', 4, 5]
```

では、「印字（表示）可能文字の文字コードの値」の範囲はどこまでかとなると、「**32**」から「**126**」までとなっていました。

```elixir

ml = 32..126 |> Enum.map(fn x -> x end)
' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
```

例えば、全体に+1すると、「印字（表示）可能文字の文字コードの値」以外の値がリストに入るので、文字リテラルではなく、数値のリストとして表示されます。

```elixir

ml |> Enum.map(fn c -> c + 1 end)
[33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, ...]
```

もう少しわかりやすい例を。

101から126までの数値のリストに対して、「-1」と「+1」を実施した例です。
「印字（表示）可能文字の文字コードの値」の**126**を超えた値がリストに入ると、文字リテラルは数値のリストとして表示されるようになります。

```elixir
iex> ml |> Enum.filter(fn c -> c > 100 end)
'efghijklmnopqrstuvwxyz{|}~'

iex> ml |> Enum.filter(fn c -> c > 100 end) |> Enum.map(fn c -> c - 1 end)
'defghijklmnopqrstuvwxyz{|}'

iex> ml |> Enum.filter(fn c -> c > 100 end) |> Enum.map(fn c -> c + 1 end)
[102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]
```

## まとめ

書籍「プログラミングElixir」の「**第7章 リストと再帰**」「**第11章 文字列とバイナリ**」を読み返しながら整理してみました。

単一引用符で囲んだ際に出来上がった「文字のリスト」は「数値[^3]のリスト」でもあります。
iex側でどちらかの区別がつかないために、印字可能な文字を示す数値であれば、文字のリスト、すなわち文字リテラルとして判断して表示しています。

ヘッドとテイルの組み合わせ（`[head|tail]`）を設定した際に表示されるのは、全体のリストです。
その中で、ヘッドがリストの先頭要素でありながら値として「文字のリスト」でもある場合には、文字リテラルとして表示されます。同様に、テイルを構成しているリストの中に「文字のリスト」があれば、やはりその要素の値は文字リテラルとして表示されます。

他のプログラミング言語とは異なり、Elixirでは単一引用符と二重引用符の違いを意識して扱う必要があります。



[^3]: 文字を示す文字コードの整数値