第2回 Erlang 分散システム勉強会 に参加させて頂きました

勉強会ってぇのは初めての参加だったのですが、良いものですね!
普段よく拝見させて頂いている blog オーナーの方々とお会いする事が出来、ヒジョーに刺激的でした。

懇親会でもどなたかがおっしゃってましたが、 Erlang の勉強会というよりは、分散システムに重きを置いた勉強会でしたね。

Process Design and Polymorphism: Lessons Learnt from Development of Kai |資料

たけまるさんによる発表。
パフォーマンスを上げるために、どのような処理を spawn すべきか、すべきではないのかというお話がとても参考になりました。

  • spawn しなきゃだめ
    • 外部から呼ばれるモジュール
  • spawn する必要は無い
    • Stateless なモジュール
  • spawn した方が良い
    • Stateful なモジュール
    • ただし、プロセスを生成する場合には、State の一貫性を保つ為に、 1 プロセスで行った方が良い。また、「待ち状態」を防ぐために、そのプロセスが更に別のプロセスを呼び出すことは避けた方が良い。

また、Java で言うところの、interface や abstract class を Erlang で実装してみるというアイディアもおもしろかったです。
詳しくは、スライドで!

Logical Clocks | 資料

id:ita-wasa (a.k.a. shino) さんによる発表。

分散システムの中では、並列イベントを一意に順序付けられないよ。じゃあ、どうやってそれを解決しましょうか。というお話。

         ,. -‐'''''""¨¨¨ヽ
         (.___,,,... -ァァフ|          あ…ありのまま 今 起こった事を話すぜ!
          |i i|    }! }} //|
         |l、{   j} /,,ィ//|       『おれは Erlang 勉強会に参加したと
        i|:!ヾ、_ノ/ u {:}//ヘ        思ったらいつのまにか特殊相対性理論の講義が始まった』
        |リ u' }  ,ノ _,!V,ハ |
       /´fト、_{ル{,ィ'eラ , タ人        な… 何を言ってるのか わからねーと思うが
     /'   ヾ|宀| {´,)⌒`/ |<ヽトiゝ        おれも何をされたのかわからなかった
    ,゙  / )ヽ iLレ  u' | | ヾlトハ〉
     |/_/  ハ !ニ⊇ '/:}  V:::::ヽ        頭がどうにかなりそうだった…
    // 二二二7'T'' /u' __ /:::::::/`ヽ
   /'´r -―一ァ‐゙T´ '"´ /::::/-‐  \    分散システムだとか時計合わせだとか
   / //   广¨´  /'   /:::::/´ ̄`ヽ ⌒ヽ    そんなチャチなもんじゃあ 断じてねえ
  ノ ' /  ノ:::::`ー-、___/::::://       ヽ  }
_/`丶 /:::::::::::::::::::::::::: ̄`ー-{:::...       イ  もっと恐ろしいものの片鱗を味わったぜ…


出だしから特殊相対性理論の話しが出てきて、おいてけぼりにされそうでしたが、
肝心の lamport clock と vector clock のお話しはとても丁寧にして頂き何とか理解できました。
以前はよく分からなかった、この辺りの資料も分かるようになったよ! 最高!

Ermlia: Erlang implementation of Kademlia |資料

id:cooldaemon さんによる DHT アルゴリズムの一つである Kademlia についての発表。

Kademlia の良いところ
churn 耐性がある (ノードの参加・離脱が容易)

DHT の知識が乏しく、殆ど理解できませんでした。予習して行けば良かった。。すいませんすいません。。

Comet on mochiweb |資料

id:Voluntas さんによる mochiweb のお話し。

今回の勉強会の中で唯一デモがあり、また身近な web サーバのお話しということで、とても楽しかったです。
あとでやる → mochiweb のソースを追ってみる。

[LT] Regexp, Perl, and Port

id:kdaiba さんによる、 erlang から Perl 経由で正規表現使おうぜ! というお話。

Eshell で 「寿」という文字を表示出来たそう。え、UTF-8 使えるの!? てっきりUnicode はまだサポートしていないのかと。。。

Perl のライブラリを使うと、うまく表示させられるのかなぁ。出来るのならおもしろいなぁ。。

auto-complete.el で コード補完

テキスト入力中に補完候補を自動的に表示してくれる auto-complete.el をリリースしました。 Visual Studio とか Eclipse には昔から搭載されている一般的な補完機能なのですが、どういうわけか Emacs にはないようなので作ってしまいました

なにこれ! 超便利!

バッファの中から単語と思われるものを探しだし、入力補完を行ってくれます。
これで一段と気持ちよくコーディング出来そうですね^^

導入

本家を参照。

erlang-mode に対応

global-auto-complete-modeが有効になると変数ac-modesに登録されているメジャーモードのみ有効になる。

erlang-mode を起動する度に毎回手動で M-x auto-complete-mode とするのは切ないので、
ac-modes に erlang-mode を加えます。

;; append elrang-mode
(setq ac-modes
  (append ac-modes
    (list 'erlang-mode)))

使う

入力すると、バッファ内で使われている単語を候補として表示してくれます。
インクリメンタルサーチ。気持ち良い。

Tab/C-n
次の候補を選択
C-p
前の候補を選択
Return/C-m
補完の実行

lists:foldl ⊇ lists:map ⊇ lists:foreach

lists:foldl、lists:map、lists:foreach どれもリストの各要素に対する操作だけど、どう使い分ければ良いんだろう。

リストの各要素に対して(戻り値なしで)何らかのアクションを行う必要がある場合はlists:foreachを使おう。 リストの各要素について何らかの計算を行う必要がある場合はlists:mapを使おう。 リストの各要素の値を累計する必要がある場合はlist:foldlかlist:foldrを使おう。 listsモジュールには他にもいくつかの手軽なループが提供されている。listsモジュールのドキュメントに目を通して何が使えるかということを知っておくとよいだろう。

なるほど。

lists:fold(l|r)
戻り値有り & 要素の集計を行う
lists:map
戻り値有り & 各要素に対し計算を行う
lists:foreach
戻り値無し & 各要素に(計算というよりは)アクションを起こす

lists:foldl、lists:map、lists:foreach のうち、 一つだけ無人島に連れていけるとしたら?

ボクは lists:foldl ちゃん!
lists:map も lists:foreach も lists:foldl を使って簡単に実装出来るよ!

my_lists.erl

-module(my_lists).
-compile(export_all).

%% lists:map を lists:foldl で実装
map_by_foldl(Fun, L) ->
    L1 = lists:foldl(fun(E, AccIn) ->
			[Fun(E)|AccIn] end, [], L),
    lists:reverse(L1).

%% lists:foreach を lists:foldl で実装
foreach_by_foldl(Fun, L) ->
    map_by_foldl(Fun, L),
    ok.

%% lists:foreach を lists:map で実装
foreach_by_map(Fun, L) ->
    lists:map(Fun, L),
    ok.

実行

> lists:map(fun(X) -> X * 2 end, L).
[2,4,6,8,10]
> my_lists:map_by_foldl(fun(X) -> X * 2 end, L).
[2,4,6,8,10]
> lists:foreach(fun(X) -> io:format("*~w*~n",[X]) end, L).
*1*
*2*
*3*
*4*
*5*
ok
> my_lists:foreach_by_foldl(fun(X) -> io:format("*~w*~n",[X]) end, L).
*1*
*2*
*3*
*4*
*5*
ok

わーい。おんなじだー >w<

lists:foldl ⊇ lists:map ⊇ lists:foreach なのかな。
逆向きはややこしいワイ。

ちなみに 本家の実装

/usr/local/lib/erlang/lib/stdlib-1.15.2/src/lists.erl

%% 中略
map(F, [H|T]) ->
    [F(H)|map(F, T)];
map(F, []) when is_function(F, 1) -> [].
%% 中略
foldl(F, Accu, [Hd|Tail]) ->
    foldl(F, F(Hd, Accu), Tail);
foldl(F, Accu, []) when is_function(F, 2) -> Accu.
%% 中略
foreach(F, [Hd|Tail]) ->
    F(Hd),
    foreach(F, Tail);
foreach(F, []) when is_function(F, 1) -> ok.

こっちのが断然スマート。

lists:foldl/3

つい何年か前までは「fold って何?うまいの?」とか言ってたわしだけど、すっかり関数型脳になった今では fold が無い生活なんて考えられない。

map やら foreach は分かるけど、 fold が良く分かりません!
ボクも fold を使いこなしたいです >w<
ということで、調べてみました。

こんな風に使います

> lists:foldl(fun(X, Sum) -> X+Sum end, 0, lists:seq(1,10)).
55

リスト [1,2,3,4,5,6,7,8,9,10] のそれぞれの要素を 足していく式です。
うーん。分ったような分からんような。

それぞれの引数が何を表しているのか、どういう流れで値が変化しているのかが分りにくいや!

困ったときは原典に

foldl(Function, Acc0, List) -> Acc1

Types:
Function = fun(A, AccIn) -> AccOut
List = [A]
Acc0 = Acc1 = AccIn = AccOut = term()

Acc0 is returned if the list is empty.

引数が何を表しているのかに注目します。

  • lists:foldl/3 の引数を、頭から Function, Acc0, List とします。
    • Function は arity 2 (= 引数の数が 2 つ) の関数です。それぞれ A, AccIn とします。
      • A はリストの要素です。(foldl の場合、リストの左から要素を取り出します。)
      • AccIn は、現在の状態です。
    • Acc0 は、初期状態です。
    • L は処理対象の要素のリストです。
    • L が 空リスト (= []) の場合、結果は Acc0

最初の例を振り返ると、リストの各要素に対して、
新しい状態 = 要素 + 現在の状態
を行っていることになるんですねー。

動きを詳しく追う

lists:foldl/3 を使った階乗のサンプルの動きを追ってみましょう。

% 5 の階乗
> L = lists:seq(1,5).
[1,2,3,4,5]
> Acc0 = 1.
1
> lists:foldl(fun(E, AccIn) -> E*AccIn end, Acc0, L).
120

状態が変化しつつ持ち越されていることがポイントです。

  1. リストの最初の要素 1 と 初期状態 1 との積 1 が新しい状態となる
  2. リスト次の要素 2 と 現在の状態 1 との積 2 が新しい状態となる
  3. リスト次の要素 3 と 現在の状態 2 との積 6 が新しい状態となる
  4. リスト次の要素 4 と 現在の状態 6 との積 24 が新しい状態となる
  5. リスト次の要素 5 と 現在の状態 24 との積 120 が新しい状態となる

表にするとこんな感じです。

Elem AccIn AccOut
1 1 1
2 1 2
3 2 6
4 6 24
5 24 120

一般化してみます。

  1. 初期状態 (=Acc0) とリストの最初の要素 (=E0) とで Function し、新しい状態 (=Acc1) になる
  2. Acc1 と E1 とで Function し、Acc2 になる
  3. Acc2 と (ry
  4. AccN-1 とEN-1 とで Function し、最後の状態 となる
  5. 最後の状態 を 返す

foldr と foldl

ちなみに、foldr より foldl の方が末尾再帰を利用しており効率的なのだそうです。
特別な理由がない限り foldl を使うようにすると幸せになれるかもしれません。

foldr differs from foldl in that the list is traversed "bottom up" instead of "top down". foldl is tail recursive and would usually be preferred to foldr.

OTP R12B-5 がリリースされました

主な変更点

  • 単体テストモジュールが同封されました
  • ユーザ定義属性に FuncName/Arity が使えるようになりました
  • その他いろいろ readme を見てね

# Eunit, an application for unit testing of Erlang modules is now part of the distribution.
# User defined attributes can now contain the syntax FuncName/Arity. (An implementation of EEP-24.)
# Read the complete list of bug fixes and new functions in the readme file.

噂になっていた Unicode サポートは入ってないのかしら。

あとでやる → Eunit で遊ぶ

プログラミング Erlang 誤植

p. 287

誤)

compute_area({rectangle, X, Y}) -> X*Y.

正)

compute_area({rectonge, X, Y}) -> X*Y.

「故意のスペルミス」を直してしまったみたい。

p.291 での呼び出しでエラーを起こしたいので、
「正しい (意図的な) エラー」を仕込みましょう :)

ohmsha では正誤表が見つからなかったけれど、本家の方に記載されてた。

#33108: The 1.0 version had the "correct" (i.e. intentional) error of misspelling "rectangle" as "rectonge". 2.0 "fixes" the problem, leading to confusion.

(The paragraph above the example says "it contains a deliberate error (can you find it?)" which refers to this error.)--Steven Grady

Joe かわいいよ。Joe。

You’re right – I’m an idiot. I’m the nitwit who “fixed” it. Silly me.

The last clause of compute_area/1 in area_server.erl should be:

compute_area({rectonge, X, Y}) -> X * Y.

Glad you’re enjoying the book.

Cheers

/Joe Armstrong

escript 注意点

Erlangスクリプト (=escript) を書く際、結構ハマッたのでメモメモ

escript には main/1 関数が必須

スクリプトを実行する際に、 main/1 関数が呼ばれる。

An Erlang script file must always contain the function main/1. When the script is run, the main/1 will be called with a list of strings representing the arguments given to the script

プリプロセッサディレクティヴは使えない

  • include_lib は例外的に使える。

Pre-processor directives in the script files are ignored, with the exception for the -include_lib directive.

定義済マクロは使えない

モジュール名が無いため、 ?MODULE なんかは escript 内で使えない。

Pre-defined macros (such as ?MODULE) will not work. A script does not have module name, so BIFs such as spawn/3 that require a module name cannot be used. Instead, use a BIF that take a fun, such as spawn/1.