Elixir入門 06: バイナリと文字列および文字リスト

gumitech

gumi TECH

Posted on October 23, 2018

Elixir入門 06: バイナリと文字列および文字リスト

本稿はElixir公式サイトの許諾を得て「Binaries, strings, and charlists」の解説にもとづき、加筆補正を加えてElixirのバイナリと文字列および文字リストについてご説明します。

UTF-8とUnicode

文字列はUTF-8エンコーディングのバイナリです。バイナリであることはis_binary/1関数で確かめられます。はじめにバイトとコードポイントの違いについて確かめておきましょう。

iex> is_binary("hello")
true
Enter fullscreen mode Exit fullscreen mode

Unicode標準は数多くの文字にコードポイントを割り振っています(「Unicode」参照)。たとえば、「a」のコードポイントは97です。文字列はコードポイントを並べて表します。そして、コンピュータで扱うために数値はバイトに直さなければなりません。1バイトは0から255までの整数です。

ところが、日本語の「あ」はコードポイント12354です。つまり、ひとつの文字が1バイトでは表せないことになります。そこで、エンコーディングが求められるのです。Elixirがデフォルトで採用するUTF-8のエンコーディングは、数バイトを使って文字のコードポイントを定めます。文字列はUTF-8エンコーディングで管理されたコードポイントを示すバイトの集まりといえるのです。

基本的なアルファベットは1バイトずつで表せるのに対して、日本語の文字はひと文字で複数バイトを使います。文字列のバイトサイズは関数byte_size/1、文字数はString.length/1調べればよいでしょう。

iex> byte_size("hello")
5
iex> byte_size("拝啓")
6
iex> String.length("hello")
5
iex> String.length("拝啓")
2
Enter fullscreen mode Exit fullscreen mode

また、文字のコードポイントは?で得られます。

iex> ?a
97
iex> ?あ
12354
Enter fullscreen mode Exit fullscreen mode

さらに、文字列をひと文字ずつのリストにして返すのが関数String.codepoints/1です。

iex> String.codepoints("hello")
["h", "e", "l", "l", "o"]
iex> String.codepoints("拝啓")
["拝", "啓"]
Enter fullscreen mode Exit fullscreen mode

バイナリ

Elixirのバイナリは、関数<<>>/1にバイトシーケンスを渡して定めます。

iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size(<<0, 1, 2, 3>>)
4
iex> <<230, 139, 157, 229, 149, 147>>
"拝啓"
iex> byte_size(<<230, 139, 157, 229, 149, 147>>)
6
Enter fullscreen mode Exit fullscreen mode

バイトシーケンスはさまざまに操作できます。文字列を表すこともありますし、そうでないこともあります。文字列として有効かどうか確かめるのはString.valid?/1メソッドです。

iex> String.valid?(<<239, 191, 19>>)
false
iex> String.valid?(<<230, 139, 157, 229, 149, 147>>)
true
Enter fullscreen mode Exit fullscreen mode

文字列を結ぶ<>/2演算子は、バイナリをつなぐこともできます。Elixirにおける文字列は、バイナリとして扱われているからです。つまり、<>/2は実はバイナリを結ぶ演算子なのです。

iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>
iex> <<230, 139, 157>> <> <<229, 149, 147>>
"拝啓"
Enter fullscreen mode Exit fullscreen mode

文字列のバイトシーケンスは、0バイト<<0>>をつなげば確かめられます。

iex> "拝啓" <> <<0>>
<<230, 139, 157, 229, 149, 147, 0>>
Enter fullscreen mode Exit fullscreen mode

バイナリの各バイトは0から255までの整数で表されます。256以上の整数を与えても構いません。デフォルトでは剰余が取られます。また、size/1によるビット数やエンコーディングの指定により、値を変換することも可能です。

iex> <<256>>
<<0>>
iex> <<256 :: size(16)>>
<<1, 0>>
iex> <<256 :: utf8>>
"Ā"
iex> <<256 :: utf8, 97>>
"Āa"
iex> <<256 :: utf8, 97, 0>>
<<196, 128, 97, 0>>
Enter fullscreen mode Exit fullscreen mode

1ビット(size(1))に変換すると、値は0か1にかぎられます。すると、バイナリでなくビットストリングとして扱われるのです。バイナリかビットストリングかは、それぞれ関数is_binary/1is_bitstring/1で確かめられます。

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
Enter fullscreen mode Exit fullscreen mode

バイナリも8ビットずつに分けたビットストリングです。

iex>  is_binary(<<1 :: size(16)>>)
true
iex> is_bitstring(<<1 :: size(16)>>)
true
iex>  is_binary(<<1 :: size(15)>>)
false
Enter fullscreen mode Exit fullscreen mode

バイナリやビットストリングにパターンマッチングを使うこともできます。

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>>
Enter fullscreen mode Exit fullscreen mode

バイナリの各バイトは8ビットとみなされます。サイズのわからないバイナリを加えたいときは、パターンの最後にbinaryの指定を添えることでマッチさせられます。

iex(86)> <<97, x :: binary>> = <<97, 196, 128>>
"aĀ"
iex(87)> x
"Ā"
Enter fullscreen mode Exit fullscreen mode

また、<>演算子でもパターンマッチングが使えます。

iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"
Enter fullscreen mode Exit fullscreen mode

文字リスト

文字リストはコードポイントのリストです。シングルクォーテーション('')でかこんだリテラルによりつくることもできます。iexでは、ASCII範囲外の文字が含まれると、コードポイントのリストが示されます。Elixirでは、ダブルクォーテーション("")は文字列つまりバイナリ、シングルクォーテーション('')は文字リストを表すということです。

iex> 'hello'
'hello'
iex> '拝啓'
[25309, 21843]
Enter fullscreen mode Exit fullscreen mode

文字リストはErlangとのインタフェースによく使われてきました。文字列と文字リストの間の変換には、関数to_string/1to_charlist/1を用います。なお、to_string/1の引数に渡せるのはリストだけにかぎりません。

iex> to_charlist("hello")
'hello'
iex> to_charlist("拝啓")
[25309, 21843]
iex> to_string('hello')
"hello"
iex> to_string([25309, 21843])
"拝啓"
iex> to_string(:hello)
"hello"
iex> to_string(1)
"1"
Enter fullscreen mode Exit fullscreen mode

文字列つまりバイナリをつなぐには<>/2演算子を用いました。けれど、文字リストは++/2演算子で結ばなければなりません。

iex> 'this ' <> 'fails'
** (CompileError) iex:n: 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:n: (file)
iex> 'this ' ++ 'works'
'this works'
iex> "he" ++ "llo"
** (ArgumentError) argument error
    :erlang.++("he", "llo")
iex> "he" <> "llo"
"hello"
Enter fullscreen mode Exit fullscreen mode

Elixir入門もくじ

番外
💖 💪 🙅 🚩
gumitech
gumi TECH

Posted on October 23, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related