Erlang入門

Erlangの特徴は並列処理を言語やVMでサポートしている関数型言語です。
また、ErlangはPrologやその後継である並列論理型言語の強い影響を受けていて構文やVMの構造などにその影響が見られます。

インストール

Windows

http://www.erlang.org/downloads
よりインストーラをダウンロードしてインストールしてください。

Mac

brew install erlang

でインストールできます。

Linux

Debian系なら

apt install erlang

対話環境

Windows

"スタート" -> "全てのプログラム" -> "Erlang OTP " -> "Erlang"

Mac & Linux

ターミナルより

$ erl

と打ち込むことで対話環境が立ち上がります。
ためしに 1+2+3.と打ち込んで見ましょう。

$ erl
Erlang R16B03 (erts-5.10.4) [source] [64-bit] [async-threads:10] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
1>
6
2>

6と表示されます。これは式1 + 2 + 3.が計算された結果です。Erlangでは;の代わりに.が使われます。

コンパイル

erlcコマンドでコンパイルします。
ErlangはBEAMというVM上で実行されるので、VM上で動作するバイナリにコンパイルされます。
こうして出来上がったファイルをbeamファイルと呼びます。
作成したファイルは

$ erl -pz . -noshell -noinput -s モジュール名 関数名 -s init stop

とタイプしてターミナルで実行することができます。

しかし、手軽に実行させたい場合はescriptを使用するほうが便利でしょう。

escript

escriptではErlangのプログラムをPerlスクリプトのように実行することができます。

#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname factorial -mnesia debug verbose
main([String]) ->
    try
        N = list_to_integer(String),
        F = fac(N),
        io:format("factorial ~w = ~w\n", [N,F])
    catch
        _:_ ->
            usage()
    end;
main(_) ->
    usage().

usage() ->
    io:format("usage: factorial integer\n"),
    halt(1).

fac(0) -> 1;
fac(N) -> N * fac(N-1).
$ chmod u+x factorial
$ ./factorial 5
factorial 5 = 120
$ ./factorial
usage: factorial integer
$ ./factorial five
usage: factorial integer
$ escript factorial 5

などとします。

詳しくはescript manual1を参照してください。

整数と実数

4> 2#0101.
5
5> 16#deadbeaf.
3735928495

のように#のまえに数字を置くことで36進数までN進数表記ができます。
Erlangは多倍長整数です。
浮動小数点数はIEEE754形式で$1.0e-323$から$1.0e+308$の範囲です。

文字列

文字はASCII文字の前に$を付けます。

7> $a.
97

文字列はダブルクォートで囲みます。これは文字のリストの糖衣構文です。リストは何かは後述します。

8> "a".
"a"
9> [$a,$b].
"ab"

Erlangの文字列は整数型のリストの糖衣構文であるため、多バイト文字は表現できません。
後述するバイナリで表現する必要があります。

変数

Erlangの変数名は大文字から始まります。

10> X = 1.
1
11> X.
1

これは変数X1を代入して、Xを評価しています。
代入については後述するパターンマッチで説明します。

演算子

算術演算子

  • +
    加算

  • *
    乗算

  • -
    減算

  • /
    除算

  • div
    整数の商

  • rem
    整数の商の余り

基本的にC言語やJavaと同じです。divremはそれぞれ整数で割ったときの商と余りです。

比較演算子

  • >
    大なり

  • <
    小なり

  • >=
    大なりイコール

  • =<
    小なりイコール

  • ==
    等しい

  • /=
    等しくない

  • =:=
    等しい(型チェック付き)

  • =/=
    等しくない(型チェック付き)

論理演算子

  • not
    否定

  • and
    論理積∧

  • andalso
    論理積∧(短絡)

  • or
    論理和∨

  • orelse
    論理和∨(短絡)

  • xor
    排他的論理和

条件分岐

if

if
    <条件式> -> <式1>;
    <条件式> -> <式2>
end

Erlangにはパターンマッチがあるためifはあまり使われません。
条件分岐は後述するパターンマッチの方をよく使います。

アトム

アトムは識別子を表します。小文字英文字から始まる英数字か、記号を含む場合はバッククォートで囲む必要があります。

1> abc.
abc

もちろん変数に代入もできます。

2> X = abc.
abc
3> X.
abc

アトムは後述するタプルやメッセージなどをパターンマッチするときに便利なのでErlangプログラミングではよく使用されます。

出力

Erlangではコンソールへの出力は一般にはio:formatという関数を使って行います。

io:format

Erlangによる書式つき出力はio:formatをつかって行われます。C言語のprintfに相当しますが、フォーマット付き出力の書式はCよりCommon Lisp//コモン リスプに似ています。

io:format("~p~n",[Term]).
  • ~p プリティープリント
  • ~s 文字列
  • ~n 改行
  • ~d 整数(10進数)

ErlangにはC言語のような可変引数がないのでリストを使います。

パターンマッチ

case節

case <式> of
    <パターン1> -> <式1>;
    <パターン2> -> <式2>
end

case節はパターンマッチの一番わかり易い形式です。

2> A = 3.
3
3> case A of 1 -> a; 2 -> b; 3 -> c end.
c

とするとcが返ります。
このようにC言語のswitch文のように使うことも出来るのです。
この式から最後のパターンマッチの処理を除くとどうなるでしょうか?

4> case A of 1 -> a; 2 -> b end.
** exception error: no case clause matching 3
5>

このように例外が発生します。これはパターンマッチにマッチする条件がなかったと言う意味の例外です。

ワイルドカード

では次のように変更するとどうでしょうか?

5> case A of 1 -> a; 2 -> b; _ -> c end.
c

_はワイルドカードといってあらゆる場合にマッチします。正規表現の.*に似ています。予期しない値などのエラー処理に使われることがよくあります。

単一代入

X = 1.

 先程代入といったのは実はパターンマッチなのです。
=演算子は左辺の式に対して右辺のパターンマッチを行う演算です。左辺の変数がまだ束縛されてなければ右辺の式を評価した値が束縛されます。
=の左辺式のパターンマッチが失敗するともちろん例外が発生します。

この例では数値型のような単純な型の場合ばかりなので有用性が分かりづらいと思います。
後述するリストやレコード、タプルなどと組み合わせることで非常に多様な使い方ができます。
パターンマッチはErlangの中核となる機能です。

関数

<関数名>(仮引数...) ->
<本体>.

例えば以下は引数に指定された変数をインクリメントする関数です。

inc(X) -> X+1.

このようにして関数定義を行います。

関数とパターンマッチング

Erlangは関数の引数にもパターンマッチを行うことができます。

signal(red) -> stop;
signal(blue) -> do;
signal(yello) -> carefull;
signal(_) -> error.

再帰

ErlangにはC言語におけるwhile文などのループ制御構文がないので、ループ処理は再帰で行います。
階乗を求める関数を書いてみます。

pow(1) -> 1;
pow(X) -> X*pow(X-1).

末尾再帰

末尾再帰という形式にすることによってループ処理を最適化できます。

pow_tail(1,Ret) -> Ret;
pow_tail(X,Ret) -> pow_tail(X-1,X*Ret).

 末尾再帰形式とは簡単にいうと、再帰関数の帰り値を評価しないのでスタックフレームに関数の戻り先を毎回積まなくてもいいので、
内部でwhileループのように扱えるのです。気になった人は自分で勉強してください。

レコード

いわゆる構造体です。

-record(<レコード名>, {<要素名1>,<要素名2>,...}).

レコードを定義するrd関数と言う糖衣構文も用意されています。

rd(item, {id,name,price}).
7> rd(player, {name,hp}).
player

初期化

#<レコード名>{<要素名1>=初期値1, <要素名2>=初期値2,...}

といった形でレコードの初期化が可能です。

9> P = #player{name="hoge", hp=3}.
#player{name = "hoge",hp = 3}

参照

変数#<レコード名>.<要素>

と言った形で参照します。

10> P#player.name.
"hoge"

変更

変数#<レコード名>{<要素>="hage"}.

とすることでレコードの要素を変更したレコードを返します。

11> P#player{name="hage"}.
#player{name = "hage",hp = 3}

レコードのパターンマッチ

左辺に、

#<レコード名>{<要素名1>=値,<要素名2>=変数名,..}

と言った形でパターンマッチできます。

2> P = #item{id=1,name="test",price=100}.
3> #item{id=1} = P.
#item{id = 1,name = "test",price = 100}
4> #item{id=1,name=Name} = P.
#item{id = 1,name = "test",price = 100}
5> Name.
"test"

リスト

[<要素1>,<要素2>...]
1> [1,2,3].
[1,2,3]

これはLispなどのリストと同じリストです。内部的にはLispと同様consセルによる再帰構造で最後がnilとなっています。この事は後述します。

リスト処理

リストはパターンマッチで処理が可能です。
次にリストの長さを計算する関数を例として載せます。

len([]) ->
    0;
len([Head|Tail]) ->
    1+len(Tail).

[]は空のリストにマッチします。
[Head|Tail]Headはリスト先頭、たとえば[1,2,3]とあったら1にマッチしてTail[2,3]にマッチします。

リストを処理する関数は標準ライブラリのlistsというモジュールにまとめられています。

タプル

{<要素1>,<要素2>...}
1> {1,2,3}.
{1,2,3}

タプルはErlangではLispのS式代わりに使われることが多いです。

バイナリ

<< <値1>:<サイズ1>/<タイプ>, <値2>:<サイズ2>/<タイプ> ...>>

というふうにかいて値をサイズ毎に指定できます。サイズ指定を省略するとデフォルトの8ビットになります。
このサイズの大きさごとに分割された塊をセグメントと呼びます。

タイプは型、符号、エンディアンが指定でき、複数のタイプを-で連結できます。
指定可能な型を以下に載せます。

  • integer 整数
  • float 浮動小数点
  • binary バイナリ型
  • bytes バイト型
  • bitstring ビットストリング
  • bits ビット型
  • utf8 UTF-8のビットストリング
  • utf16 UTF16のビットストリング
  • utf32 UTF32のビットストリング

符号

  • signed
    符号付き

  • unsigned
    符号なし

エンディアン

  • big
    ビッグエンディアン

  • little
    リトルエンディアン

  • native
    CPUネイティブの エンディアン

  • unit
    セグメントのサイズ

バイナリ演算子

  • bsl 左シフト
  • bsr 右シフト
  • band bit論理積
  • bor bit論理和
  • bxor bit排他的論理和
  • bnot bit論理否定

バイナリのパターンマッチ

バイナリのパターンマッチの例としてRGBカラーの例を載せます。

1> Color = <<16#FF00FF:(8*3)>>.
<<255,0,255>>

としてRGBのそれぞれの8ビットを,で区切って定義することができます。

2> <<R,G,B>> = Color.
<<255,0,255>>

この様にパターンマッチによって8ビットごとに分解したりすることができます。

先頭のRだけほしい場合は、ワイルドカードを使って次の様に書くことで取得できます。

2> <<R:8,_/binary>> = Color.
<<255,0,255>>
3> R.
255

余談ですが、Erlangで日本語のような多バイト文字を扱うには

io:format("~ts~n",[<<"お"/utf8>>]).

のようにバイナリ形式でUTF-8を指定する必要があります。詳しくはErlangのドキュメントをご覧ください。2

内包表記

内包表記にはリスト内包表記とバイナリを内包表記の二種類があります。
もともとは、内包表記とは集合論において条件Pに合致する要素を集めた集合を表現する記法です。
これを表現しようとした記法がMirandaに取り入られました。この記法は後にHaskellや[Pythonにも取り入れられています。

リスト内包表記

erl

Erlang R16B03 (erts-5.10.4) [source] [64-bit] [async-threads:10] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
1> [ X*2 || X<-[1,2,3,4,5], X<4 ].

と打ち込んでみてください。
すると、

[2,4,6]

帰ってくるはずです。
これは、X<-[1,2,3,4,5]によって、リストが生成されます。
次に、条件X<4に合致する1,2,3がフィルタリングされ、
最後に式X*2によって1*2,2*2,3*2のリストが作られたということです。

一般化すると次のようになります。

[式 || パターン <- リスト, 条件]

バイナリ内包表記

バイナリ内包表記は内包表記のバイナリ版です。

 <<式 || パターン <= バイナリ, 条件>>

並列

Erlangにおいて並列処理は、軽量プロセス(以下プロセス)とメッセージによって実現されています。
ErlangのプロセスはOSのプロセスに似ていますが、ErlangのVMで実行されるのでOS内部でのコンテキストスイッチが行われません。
Erlangのプロセス同士はメモリ領域を共有せずにメッセージ・キューを通じて通信を行います。
Erlangのプロセスはレジスタの一時退避やカーネルモードへの切り替えなどの複雑なコンテキストスイッチを伴わないため切り替えは高速です。

アクターモデル

Erlangの並列モデルであるアクターモデルにについて簡単に紹介します。
メッセージを送受信できるアクターと呼ばれるオブジェクトが、メッセージを非同期に送受信することによって、
並列的に計算処理を行う計算モデルです。Erlangにおいてはプロセスがアクターに相当し、お互いにメッセージを送受信しています。
このアクターモデルの概念はErlang以外にもScalaのフレームワーク等に採用されています。

spawn

spawnは次の様な形で呼び出して軽量プロセスを生成します。

Pid = spawn(<モジュール名>, <関数名>, <引数のリスト>)

spawnは引数の関数からプロセスを生成し、プロセスIDと呼ばれるプロセス毎にユニークな数値を返します。
プロセスIDとプロセスは紐付いていて、プロセス間におけるメッセージの送受信の際にこのプロセスIDを使用します。

メッセージの送信

メッセージの送信は次の様に送信したいプロセスのプロセスIDに!メッセージという形式でメッセージを送信します。

Pid ! メッセージ

メッセージの受信

次の様にreceiveでメッセージを受信し、パターンマッチでメッセージに対応する処理を行うのが一般的です。

receive
    <パターン1> ->
        <処理1>;
    <パターン2> ->
        <処理2>;
    ............
    <パターンN> ->
        <処理N>
    after n ->
        <タイムアウト処理>
end

afterはnミリ秒たった時のタイムアウト時の処理をします。

プロセス登録

spawnが生成するプロセスIDをいちいちプログラマが変数で保存しておくのは面倒です。特に複数のプロセスがメッセージを送るプログラムを作るときは管理がとても面倒になります。
ErlangにはプロセスIDではなく、アトムを指定してメッセージを送る方法があります。
それがプロセス登録です。プロセスを登録するにはregister関数を使用します。
本書の範囲を超えるので詳しくは述べませんが、プロセス登録機能は分散環境ではよく使用します。

register(Atom,Pid)

とすることで、
Atom ! メッセージ
と言った形でアトムを使って登録したプロセスにメッセージが送信できます。

spawn_link

spawn_linkは基本的にspawnと同じですが、例外が発生した場合の動作が異なります。
生成した子プロセスで発生した例外をプロセスが作成した側の親プロセスも受け取ります。
spawnの場合、親プロセスは例外を受け取りません。

例外

例外は次の様にして書きます。

try <評価される式> of
    <パターン1> ガード1 -> <処理1>;
    <パターン2> ガード2 -> <処理2>
catch
    <例外のタイプ1>: <パターン1> ガード1 -> <例外処理1>;
    <例外のタイプ2>: <パターン2> ガード2 -> <例外処理2>
after
    <例外が起きても起きなくても実行される処理>
end

try ... ofで囲まれた式において例外が発生するとcatch以降でパターンマッチが行われ、例外処理が実行されたあとにafterの処理が行われます。

次は例外を発生させる関数です。

throw

次の様にして例外を発生させることができます。

throw(<式>)

exit

次の様にして例外を発生を起こし、プロセスを終了させます。

exit(<式>)

error

次の様にして重大な例外を発生させ、プロセスを終了させます。
errorは主にEralngのVMが生成するランタイムエラーとして使用されます。

error(<式>)

モジュール

モジュールはソースを記述するファイル単位でコンパイルする際の最小単位です。ソースは何らかのモジュールに属していないとコンパイルできません。
モジュールを作成するには.erlファイルに
モジュール宣言などのモジュール属性を記述する必要があります。

モジュール属性

モジュール属性はファイルの先頭によくある-から始まる宣言です。
たとえば、モジュール宣言などがあります。

モジュール宣言

-module(<モジュール名>).

エクスポート宣言

エクスポート宣言する関数を宣言します。
次の様に関数名と引数の数を/でつなげてください。

-export([<関数名>/<引数の数>])

次の様にすればモジュールで作成した全ての関数をエクスポートできます。

-export_all

コンパイルオプション宣言

コンパイラに指定するオプションです。3

マクロ定義

次の様にしてマクロを定義できます。

-define{<マクロ>,<式>}

レコード宣言

次の様にしてレコードを宣言できます。

-record(<レコード名>, {<要素名1>,<要素名2>,...}).

インクルード

次の様にしてヘッダファイルをインクルードできます。

-include("<ヘッダファイル>").

ヘッダファイルの拡張子は.hrlです。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.