👏

文字列と文字リスト -- YouTube チャンネル daimon.ex 第1回解説

2024/03/11に公開

動画の概要

https://youtu.be/B0nqleigUj0

文字列と文字リスト

動画後半のライブコーディングで話したように、プログラミング言語 Elixir には、文字列 (string) と 文字リスト (charset) という類似した概念があります。

文字列は "Helo, world!" のようにダブルクォートで囲み、文字セットは 'Helo, world!' のようにシングルクォートで囲みます。JavaScript や Ruby のような多くの言語では、ダブルクォートとシングルクォートのどちらで囲んでも文字列ができますので、これは Elixir の特徴です。

実は、Elixir プログラミングで文字リストを使用する機会はかなり限られています。ほぼ Erlang の関数を呼ぶ時の引数に指定するときにしか、文字リストは使われません。

Erlang は Elixir の親戚に当たる言語で、同じ仮想マシン(BEAM)の上で動作します。Elixir プログラムは Erlang のすべての関数を呼び出すことができます。

例えば、Erlang に zip:unzip/1 という関数があります。この関数は第1引数にファイル名を取り、その中身が ZIP 形式のアーカイブであれば、それに含まれるファイルのリストを返します。Elixir プログラムからこの関数を呼び出すには、:zip.unzip(filename) のように書きます。

関数 zip:unzip/1 の第1引数に指定するファイル名は文字リストである必要があります。もし、文字列を指定すると例外 :badmatch が発生します。

さて、文字列はバイナリ (binary) の一種であるという性質があります。そのため、is_binary("Hello, world!")true を返します。他方、文字リストはリスト (list) の一種です。そのため、is_list('Hello, world!')true を返します。

文字列と文字リストは似て非なるものです。関数 IO.puts/1 は与えられた値を文字列に変換してからターミナルに出力するので文字列でも文字リストでも引数に取れますが、そういう関数は少数派です。

参考資料

エラーメッセージを読み解く

ライブコーディング中に文字列と文字リストを演算子 <> で連結しようとして次のようなエラーメッセージが出力されましたが、その解説はスキップされました。ざっと読み解いてみましょう。

** (ArgumentError) expected binary argument in <> operator but got: ~c"Hello, world!"
    (elixir 1.16.1) lib/kernel.ex:2044: Kernel.wrap_concatenation/3
    (elixir 1.16.1) lib/kernel.ex:2031: Kernel.extract_concatenations/2
    (elixir 1.16.1) expanding macro: Kernel.<>/2
    hello.exs:1: (file)

エラーメッセージの2行目以降はスタックトレース (stack trace) です。エラーを引き起こした関数呼び出しの履歴です。2行目、3行目、4行目は、(elixir 1.16.1) で始まっているので、Elixir の内部で関数呼び出しが行われていることを示しています。

さて、注目すべきは1行目です。冒頭のカッコの中の ArgumentError は例外の名前です。続く部分を日本語に訳すと、次のようになるでしょう。

<> 演算子においてバイナリの引数を期待したが、~c"Hello, world!" を得た。

ライブコーディング中に私は <> 演算子のことを文字列同士を連結する演算子と説明しましたが、正確にはバイナリ同士を連結する演算子です。文字列はバイナリの一種なので、この演算子で連結できます。

ちょっとわかりにくいのが、エラーメッセージ1行目の末尾に現れる ~c"Hello, world!" です。この ~c は文字リストを作るためのシギル (sigil) です。Elixir では、チルダ(~)と1個の文字で始まる特別な構文が用意されていて、それをシギルと呼びます。シギルの後ろにはデリミタと呼ばれる文字のペアが置かれ、デリミタの内側にある文字列がさまざまな値に変換されます。

参考文献

Discussion