バイナリ、文字列、文字リスト

基本型の章で、我々は文字列について学び、is_binary/1 関数を使いました。

iex> string = "hello"
"hello"
iex> is_binary(string)
true

Elixir におけるバイナリがどのように文字列と関連するのか、'こういった単一引用符' がどういう意味を持っているのか。この章では、バイナリについてそれが何であるかを理解していきましょう。

UTF-8 と Unicode

文字列は UTF-8 でエンコードされたバイナリです。この意味を理解する為には、バイトとコードポイントの違いを知る必要があります。

Unicode は私たちが使う多くの文字にコードポイントを割り振っています。例えば、aという文字は97のコードポイントを持っていますが、łという文字は322のコードポイントを持っています。ディスクに"hełło"という文字列を書き込む際に、私たちはこれら文字の連なりをバイトに変換しなければならないのですが、1バイトが一つのコードポイントを表現するというルールに習った場合、"hełło"を表現することができません。コードポイント322łの為に使用していますが、1バイトでは0から255の数値を表現することしかできないのです。とはいえ、実際には"hełło"をスクリーン上で読めるのですから、 何らかの方法 でそれを表現する必要があります。そこでエンコーディングの出番です。

バイトでコードポイントを表現する際にそれらをどうにかエンコードする必要があります。Elixir はデフォルトのエンコード方式として UTF-8 を採用しています。文字列は UTF-8 でエンコードされたバイナリだと述べました。あの意味は、文字列が UTF-8 で指定された通りのコードポイントを表す為に編成されるバイトの一塊りだという意味です。

コードポイント 322 が割り振られている ł のような文字があるので、実際にはそれを表現する為に 1 バイト以上が必要になります。String.length/1byte_size/1で比較し、違いを見てみます。

iex> string = "hełło"
"hełło"
iex> byte_size(string)
7
iex> String.length(string)
5

ほら。byte_size/1は根本的にバイト数を計算しますが、String.length/1は文字数を計算していますね。

Note: Windows ではターミナルがデフォルトで UTF-8 が使えないことがあります。iex (iex.bat)を起動する前にchcp 65001を実行して現在のセッションのエンコードを変更できます。

UTF-8 はheoを表現する為にそれぞれ 1 バイトを必要としますが、łの表現には 2 バイトです。Elixir では ? を使って文字のコードポイントを得られます。

iex> ?a
97
iex> ?ł
322

the String moduleの関数を使ってそれぞれを一文字の長さに分割できます。

iex> String.codepoints("hełło")
["h", "e", "ł", "ł", "o"]

Elixir が優れた文字列操作をサポートしていることをお分かりいただけると思います。また同時に多くの Unicode 操作もサポートしています。実際、“文字列型は壊れている(英語)”という記事で提示されているすべてのテストを Elixir はパスしています。

しかし、文字列型はこの話におけるほんの一部分にでしかありません。文字列がバイナリであり、is_binary/1関数を使った時、Elixir には文字列を強化する為に基礎的な型が必要です。というわけで、それをやるとしましょう。今こそバイナリについてお話する時です!

バイナリとビット文字列

Elixir では <<>> を使ってバイナリを定義できます。

iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size(<<0, 1, 2, 3>>)
4

バイナリとはバイトのシーケンスです。これらのバイトは、どのような方法でも、文字列としては不正なシーケンスにさえ編成され得ます。

iex> String.valid?(<<239, 191, 19>>)
false

文字列の連結操作は、実際にはバイナリの連結操作です。

iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>

Elixir では、文字列の内部的なバイナリ表現を確かめる為に空のバイト <<0>>` を連結させるというテクニックをよく使います。

iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>

バイナリに与えたれている各数字はバイトを表す為であり、255 以下でなければいけません。バイナリは 255 より大きな数字を保持したり、コードポイントを UTF-8 に変換する為に修飾子を受け付けることができます。

iex> <<255>>
<<255>>
iex> <<256>> # truncated
<<0>>
iex> <<256 :: size(16)>> # use 16 bits (2 bytes) to store the number
<<1, 0>>
iex> <<256 :: utf8>> # the number is a code point
"Ā"
iex> <<256 :: utf8, 0>>
<<196, 128, 0>>

1byte(8bit) に 1bit を渡すとどうなるでしょうか。

iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # truncated
<<0::size(1)>>
iex> is_binary(<<1 :: size(1)>>)
false
iex> is_bitstring(<<1 :: size(1)>>)
true
iex> bit_size(<<1 :: size(1)>>)
1

値はもはやバイナリではありませんが、ビット文字列、つまりビットの塊です。よって、バイナリはビット数が 8 で割り切ることのできるビット文字列です。

iex>  is_binary(<<1 :: size(16)>>)
true
iex>  is_binary(<<1 :: size(15)>>)
false

バイナリやビット文字列でもパターンマッチができます。

iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>

バイナリパターンの各エントリはちょうど 8bit にマッチすることを期待されています。サイズが分からないバイナリでマッチさせたい時には、パターンマッチの最後にバイナリ修飾子を置くことによって可能です。

iex> <<0, 1, x :: binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>

文字列の連結演算子を使って、類似する結果を得ることができます。

iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"

バイナリとビット文字列のコンストラクタに関する詳細な資料はin the Elixir documentationを参照してください。これにて文字列、バイナリ、ビット文字列のツアーは終了します。文字列とは UTF-8 でエンコードされたバイナリであり、バイナリとはビット数が 8 で割り切ることのできるビット文字列でした。ここで Elixir がビットとバイトを用いた作業の為の柔軟性が用意されていることを示しましたが、99% はバイナリ操作と is_binary/1byte_size/1 を使うことになります。

文字リスト

文字リストとはコードポイントのリストにすぎません。文字リストはシングルクォーテーションを使ったリテラルで作成できます。

iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'
iex> List.first('hello')
104

文字リストはバイトの代わりに文字のコードポイントを包含していることが分かりますね (IEx は、いずれかの整数が ASCII の範囲を超える場合のみ、デフォルトでコードポイントを出力します)。ダブルクォーテーションが文字列(i.e. バイナリ) を表現するのに対して、シングルクォーテーションは文字リストを表現します(i.e. リスト)。

実際には、文字リストは特に引数としてバイナリを受け付けない古いErlangライブラリとの、インターフェイスとして使われます。 to_string/1to_charlist/1 関数を使って、文字リストを文字列に変換したり、文字列から文字リストに変換したりできます。

iex> to_charlist "hełło"
[104, 101, 322, 322, 111]
iex> to_string 'hełło'
"hełło"
iex> to_string :hello
"hello"
iex> to_string 1
"1"

これらの関数は多態的に機能します。文字リストを文字列に変換するだけでなく、整数を文字列に変換したり、アトムを文字列に変換することもできます。

文字列 (バイナリ) 連結では <> を使いますが、文字リストにはリスト連結の ++ を使います。

iex> 'this ' <> 'fails'
** (CompileError) iex:2: invalid literal 'this ' in <<>>
    (elixir) src/elixir_bitstring.erl:19: :elixir_bitstring.expand/6
    (elixir) src/elixir_bitstring.erl:12: :elixir_bitstring.expand/4
    (elixir) expanding macro: Kernel.<>/2
    iex:2: (file)
iex> 'this ' ++ 'works'
'this works'
iex> "he" ++ "llo"
** (ArgumentError) argument error
    :erlang.++("he", "llo")
iex> "he" <> "llo"
"hello"

バイナリ、文字列、文字リストについてはこのくらいにして、続いてはキーと値のデータ構造についてお話ししましょう。

Is something wrong? Edit this page on GitHub.