02 関数型言語Elixirの基礎(パターンマッチ,不変性,型)を学ぶ
はじめに
Elixir の基礎を学びます
- パターンマッチ
- 値の不変性
- 型
いずれも Elixir のプログラミングには欠かせない基礎事項です
代入ではなく束縛
a = 2
C や Python、Ruby では =
は、代入です。
Elixir では代入ではなく、束縛といいます。
関数型プログラミング言語では同様です。
左辺と右辺がマッチするという見方をします。
2 = a
と書けます。
他のプログラミング言語とくらべて数学の =
の意味により近いです。
2 = a
パターンマッチ
value
に束縛されている値は何でしょうか?
1
value = 2
value
2
{a, value} = {1, 2}
value
3
%{a: value} = %{a: 1, b: 2, c: 3}
value
4
%{a: 1, b: value} = %{a: 1, b: 2, c: 3}
value
5
[a, b, value] = [1, 2, 3]
value
6
[value | tail] = [1, 2, 3]
value
7
<> = <<1, 2, 3>>
value
8
{value} = {1, 2, 3}
value
7 の解説
<> = <<1, 2, 3>>
-
<< >>
はビットストリングを作ります。 -
<<1, 2, 3>>
は3バイト(24ビット)のデータです。 - ビットストリングは、ビットでパターンマッチできます。
-
value
を2進数で表すと0b00000001000000100000
です。10進数では4128
。
<> = <<1, 2, 3>>
0b00000001_00000010_0000 = 4128 = value
8 の解説
左辺と右辺のパターンが一致しないのでMatchError
となります。
不変性
- Elixirでは、すべての値は不変(immutable)です
- 値が不変であることから、他のプログラミング言語と比べ容易に並列化ができます
- 値が不変であることの副次的な効果は、可読性をあげ、保守しやすいプログラムとなりえます
並列化が容易
- 1から1,000,000の各要素に、foo関数とbar関数を適用しているプログラムです。
-
|>
はパイプ演算子と呼ばれるものです。次回以降の講義で取り扱います。 - ElixirではFlowというライブラリを導入するだけで、元のプログラムと似た形で並列化することができます。イミュータブルであるからこそ,並列処理にする時に値の同期や排他制御をする必要がありません。
- 並列処理性能が良いのは、イミュータブルに下支えされています。
可読性が上がる
a = [1, 2, 3]
do_something_with(a)
print(a)
-
print
にはバグはないものとし、入力値を出力する関数だとします -
print(a)
で[1,2,3]
が表示されることを期待したが、実際は[1,2,3,100]
だった -
実は
do_something_with
の呼び出しでa
に100
を追加していた
上記は簡単な短い例ですので、すべてを読むのも苦はならないかもしれませんが、現場のプログラムで量が膨大にあるときに、変数の呼び出し先まですべてを確認するのはたいへんな労力がかかります。
その点、Elixirの値はイミュータブルですから、必ず[1, 2, 3]
が表示されるプログラムしか書けません。
型
Elixirで取り扱うことができる代表的な型を説明します。他のプログラミング言語にも同様の型があります。
-
数値
- 整数
- 浮動小数点
- 真理値
- アトム
- ビットストリング、バイナリ、文字列
- タプル
- リスト
- 範囲
- マップ
数値
数値型の例を示します。
-
整数
- 1
- 0
- -1
- 0xE9 (16進数)
- 0b1000 (2進数)
-
浮動小数
- 1.0
- 3.14
- -1.2
四則演算など
加算
1 + 1
減算
1 - 2
乗算
10 * 123
除算
5 / 2
div(5, 2)
余り
rem(5, 2)
べき算
2 ** 8
大小比較
他のプログラミング言語と同じです。
-
>
-
>=
-
<
-
<=
-
==
-
!=
Elixirの大小比較で特徴的なのは異なる型の比較ができることです。 「Term ordering」というElixirの言語仕様です。
number < atom < reference < function < port < pid < tuple < map < list < bitstring
例えば、1
(number) は "awesome"
(bitstring)より小さいという決まりのため、1 < "awesome"
は正(true
)になります。
1 < "awesome"
真理値
true
false
アトム
:a
:b
:"Elixir is beautiful."
true
false
nil
たとえば、 Enum.find(1..10, fn x -> x > 10 end)
の評価結果はnil
となります
またのちほどでてきますが、1から10までの範囲データの中から10より大きいものをfind
する関数を評価していますが、該当するものがないため評価結果はnil
となります
Enum.find(1..10, fn x -> x > 10 end)
ビットストリング、バイナリ、文字列
ビットストリング
<<0b0100::4>>
<<4::4, 5::2>>
バイナリ
ビットストリングのうち長さが8の倍数であるもの
<<_::8>> = <<4::4, 3::2, 3::2>>
ビット長を省略した場合のビット長は8
<<1::8, 2::8, 3::8>> = <<1, 2, 3>>
文字列(UTF-8符号化)
"霊薬"
"霊薬" = <<0xE99C8A::24, 0xE896AC::24>>
タプル
- データの組(0〜N)を作ります。
- タプルの中には異なる型を混在させることができます。
{1, 2}
{1, 2, 3}
{1}
{}
{1, 2, :a, "abc"}
リスト
- 複数のデータを取り扱う型です。
- 異なる型のデータを混在できます。
[1, 2, 3]
[1]
[]
[1, 2, 3, :a]
[head | tail] = [1, 2, 3]
head
tail
list = [1, 2, 3]
[10 | list]
範囲
1..10
1..10//2
10..1//-1
Enum.to_list(1..10)
Enum.to_list(1..10//2)
Enum.to_list(10..1//-1)
マップ
- キーと値のペアのデータです。
- Rubyではハッシュ、Pythonでは辞書型、Javaではマップと呼ばれます。
%{"age" => 44, "name" => "osamu", "height" => 170}
%{:a => 1, :b => 2, :c => 3}
%{a: 1, b: 2, c: 3}
%{}
リファレンス
- リファレンスは、デバイスの識別などに使用する。
- 以下のプログラム例は、AHT20モジュールでの温度湿度の測定例。
{:ok, ref} = Circuits.I2C.open("i2c-1") # refがリファレンス
i2c_addr = 0x38
initialization_command = <<0xBE, 0x08, 0x00>>
Circuits.I2C.write(ref, i2c_addr, initialization_command)
trigger_measurement_command = <<0xAC, 0x33, 0x00>>
Circuits.I2C.write(ref, i2c_addr, trigger_measurement_command)
Circuits.I2C.read(ref, i2c_addr, 7)