Erlang 基礎ポイント4 - 並行処理1
今日はErlangで並行処理プログラムを作成する方法を勉強します。
並行処理
並行処理とは、一つのプログラムの中で複数の処理を同時に実行させることです。
Windows、LinuxなどのOSでは、複数のプログラムが複数のプロセスとして同時に実行されます。
Apache TomcatなどのAPサーバの内部では、複数の処理単位(リクエスト、サーバ)が複数のスレッドとして同時に実行されます。
Erlangでは、複数の処理が複数のプロセスとして実行されます。
しかし、名前はプロセスですが、ErlangのプロセスはOSのプロセスとは違います。
あくまでErlang内に存在するプロセスなのです。
これからErlangの並行処理に登場するプロセスは、Erlangプロセスと意識するようにしましょう。
基本
Erlangで作成する並行処理プログラムはJavaなどの言語で記述するスレッド基盤のプログラムとは記述方法が違います。
が、プログラム(又はメインスレッド・メインプロセス)の実行中にいきなり並行に何か(Erlangプロセス)を起動するということは同じです。
Erlangで並行処理プログラムを書くのはとても簡単ですが、それでも掘り下げると色々出てきます。
ここでは最小限の説明だけをします。後はマニュアルや他のウェブサイトを参考して、知識を拡張していけばいいと思います。
まずは、サンプルプログラムを分析し、その後、並行処理プログラムの作成方法を整理してみましょう。
% concurrent.erl -module(concurrent). -export([start/0]). start() -> % A spawn(fun() -> echo(1) end). echo(NoMsgCnt) -> % B receive shutdown -> io:format("Shutdown echo~n"); {one, Msg} -> io:format("One Msg ~p~n", [Msg]), echo(NoMsgCnt); {two, Msg} -> io:format("Two Msg ~p~n", [Msg]), echo(NoMsgCnt); Unknown -> io:format("Unknown Msg ~p~n", [Unknown]), echo(NoMsgCnt) after % C 7000 -> io:format("No Msg ~p~n",[NoMsgCnt]), echo(NoMsgCnt + 1) end.
まず、start関数内のコメントAを見てみましょう。
spawn関数が見えますね。
spawn関数を呼び出すと、引数に指定した関数が処理内容として新しいErlangプロセスが生成及び実行されます。
新しいErlangプロセスが生成されると、spawn関数を実行したErlangプロセスと新しいErlangプロセスが同時、つまり、並行で
実行されるようになります。
spawn関数はErlangプロセスのID(PID)を返します。PIDは後ほど、Erlangプロセスと通信する時に必要です。
spawn関数に指定した関数の内部ではecho関数を呼び出しています。
echo関数の内部を見てみましょう。新しいErlangの文法が出てきました。
receiveとafterですね。
基本的にspawn関数に指定する関数の内容は何でもいいです。
非同期で何かの処理を分散して行うように、新しいErlangプロセスを実行した後、そのErlangプロセスを忘れてもいい場面もあるでしょうが、
自分を実行した親Erlangプロセスはもちろんのこと、他のErlangプロセスと通信する必要がある場面がもっと多いと思います。
プロセス間の通信のため、Javaでしたら、同期しながら、変数を値を調べるとか...色々と頭が痛くなりますが、
(concurrentパッケージにあるクラスを使えば大体解決できますが、Erlangよりは確かに冗長です)
Erlangでは非常に単純にErlangプロセス間の通信が実現できます。
新しいErlangプロセスが立ち上がると、そのErlangプロセスのためのメールボックスが用意されます。
形こそ、hotmailやgmailのようなメールとは違いますが、概念的には同じです。
receive文が実行されると、メールボックスにメッセージが入ってくるまで待機(ブロッキング)状態になります。
メッセージが同時に多数届いた場合、それぞれのメッセージが非同期で処理され、処理が終わったメッセージは削除されます。
メッセージはreceive内のパターンと照合され、マッチしたパターンの処理内容が実行されます。
もし、どのパターンにもマッチしなかった場合、何も実行されません。
afterには一定の時間がすぎるまでメッセージが届いていない場合、つまり、タイムアウトした場合、
処理される内容が置かれます。単位はmsです。
上記には7000とあるので、7秒間、メッセージが届かない場合、No Msg Xと表示されます。
receive内の各パターン及びafterの処理内容を見ると、処理内容の最後に再帰呼出(echo関数の呼出)をしているのが分かります。
これはまた次のメッセージを待つためです。再帰呼出をしないと、そのままErlangプロセスが終了してしまいますので、ご注意下さい。
shutdownパターンには再帰呼出がないことから、Erlangプロセスを終了するということが分かります。
パターンにあるアトムがshutdownですから、意図的ですね。
これでErlangで並行処理を記述することができました。
次は並行処理を実行し、Erlangプロセスと通信する方法を勉強しましょう。
以下は上記のconcurrentモジュールをREPL環境でテストしたものです。
1> c(concurrent). {ok,concurrent} 2> P = concurrent:start(). <0.39.0> No Msg 1 3> P ! {one, "Hello"}. One Msg "Hello" {one,"Hello"} 4> P ! {two, "Hello"}. Two Msg "Hello" {two,"Hello"} 5> P ! {three, "Hello"}. Unknown Msg {three,"Hello"} {three,"Hello"} No Msg 2 No Msg 3 6> P ! shutdown. Shutdown echo shutdown 7>
concurrentモジュールをコンパイルし、start関数を呼び出しました。
戻り値のPIDを変数Pに代入しました。
PはPIDですが、Erlangプロセスへの電話だと考えればいいです。
Pへの連絡はどうすればいいか? ビックリマーク(!)を使えばいいです。
PID ! メッセージ
です。これがErlangプロセスにメッセージを送る方法です。簡単でしょう?
これでメッセージが該当PIDのErlangプロセスに送られると、
メッセージとErlangプロセスのreceive内にあるパターンとの照合が行われ、
マッチしたパターンの処理内容が実行されます。
整理してみましょう。
% 新しいErlangプロセスの生成は PID = spawn(fun() -> xxx(引数...) end) % Erlangプロセスでメッセージを受け取るには xxx(引数...) -> receive パターン1 -> 処理1, ... 処理x, xxx(引数...); % xxx()を再帰呼出しないと、終了してしまう。 ... パターンx -> 処理1, ... 処理x, xxx(引数...) % 最後のパターンにはセミコロンを書かない after % afterは省略可能 タイムアウト値(ms) -> 処理 xxx(引数...) end. % Erlangプロセスと通信するためには PID ! メッセージ
さらに
これで、Erlangで動く並行処理プログラムを書けるようになりましたが、
ちょっと足りない部分があります。
それぞれのErlangプロセスとやり取りするため、メッセージを送る時にspawn関数から返されるPidが必要ですが、
Pidをいちいち覚えておくのは面倒です。
そこで、ErlangにはPidを意味ある名前で登録できるようにする関数が用意されています。
その関数でPidを名前に登録しておき、後でその名前でErlangプロセスにメッセージを送ればいいです。
使い方は簡単です。以下のようにすればいいです。
register(名前を表すアトム, Pid)
反対に登録した名前を解除する関数もあります。
unregister(名前を表すアトム)
簡単ですね。
以下は基本で示したサンプルプログラムをregisterを使うように修正し、REPL環境で試したものです。
% concurrent2.erl -module(concurrent2). -export([start/0]). start() -> P = spawn(fun() -> echo(1) end), register(test_echo, P). echo(NoMsgCnt) -> receive shutdown -> io:format("Shutdown echo~n"); {one, Msg} -> io:format("One Msg ~p~n", [Msg]), echo(NoMsgCnt); {two, Msg} -> io:format("Two Msg ~p~n", [Msg]), echo(NoMsgCnt); Unknown -> io:format("Unknown Msg ~p~n", [Unknown]), echo(NoMsgCnt) after 7000 -> io:format("No Msg ~p~n",[NoMsgCnt]), echo(NoMsgCnt + 1) end. % REPL環境でテスト。 1> c(concurrent2). {ok,concurrent2} 2> concurrent2:start(). true No Msg 1 No Msg 2 No Msg 3 3> test_echo ! {one, "Hello"}. One Msg "Hello" {one,"Hello"} 4> test_echo ! {two, "Hello"}. Two Msg "Hello" {two,"Hello"} 5> test_echo ! {three, "Hello"}. Unknown Msg {three,"Hello"} {three,"Hello"} No Msg 4 6> test_echo ! shutdown. Shutdown echo shutdown 7>
PIDでなく、test_echoという分かりやすい名前でErlangプロセスと通信できていますね。
Erlangプロセスは他の言語のスレッドやOSのプロセスより早くて効率がいいです。
数万・数十万個のプロセスも問題なく処理できるそうだから、すごいですね。
自分でErlangの威力が直接感じられるプログラムが書けたらいいですね。
アイデアが浮かんだら、ぜひやってみたいと思います。
ちなみにErlangでネットワークプログラムを作ると、NodeJSと比べてどうでしょうか。
メリットが少し似ているようが気がします。ググれば出るのかな...
今日はここまでにします。
元々今日でErlang 基礎ポイントを最後にしようと思いましたが、
並行処理での例外処理について意外と書くことが多くて、
次の記事に続けて書くことにします。
最後になるかどうかは分かりません。これはあくまで個人的な理由で書くものですから...笑
Erlang 基礎ポイント3 - case式, if式 例外
case式
Erlang 基礎ポイント2 - DukeLabでパターン集合について説明しました。
パターン集合の結果から処理を複数の関数に分けることができましたが、
関数の数が無駄に多くなる恐れがありますね。
もちろん、プログラムの処理は可能な限り小さくした方が
モジュール化側面でいいでしょうが、やりすぎると、
かえって複雑なプログラムになりかねないですね。
case式を使うと、一つの関数内で処理部を分けておくことができます。
Javaのswitch caseのようなものですが、判断条件に固定値だけでなく、パターンが使えるので、
もっと強力です。
まず、例をみてみましょう。
前のguard_module.erlがErlang 基礎ポイント2 - DukeLabのパターン集合節で紹介したもので
後のcase_test.erlがcase式を使うように修正したものです。
% guard_module.erl -module(guard_module). -export([guard_test/1]). guard_test(X) when X < 5, X > 1 -> X * X; guard_test(X) when X >= 5; X < 10 -> X - 2; guard_test(X) -> X - 2. % case_test.erl -module(case_test). -export([guard_test/1]). guard_test(X) -> case X of X when X < 5, X > 1 -> X * X; X when X >= 5; X < 10 -> X - 2; X -> X - 2 end.
caseの使い方は次のようです。
他のプログラミングの経験があるなら、問題ないでしょう。
case 評価対象 of パターン1 [when ガード...] -> 結果; ... パターンn [when ガード...] -> 結果 end
最後のパターンにはセミコロンをつけないことに注意して下さい。
評価対象に判断対象(関数引数・変数など)を指定し、
パターンnに判断対象と照合するパターンを書きます。
必要ならガードに追加条件を指定できますが、ガードは省略可能です。
評価対象がパターンに一致すれば、結果が返されます。
一致するパターンがないと、例外が発生するので、注意しましょう。
caseの方が関数の長さが増えましたが、関数の数は減りましたね。
どちらも優先的に使うのではなく、関数の数とcaseの数をバランスよく
配置すればいいと思います。
if式
他のプログラミング言語のif文と同じですが、違いは式なので、戻り値があるということです。
上記のcase式のサンプルをif式に修正したものを載せます。
% if_test.erl -module(if_test). -export([if_test/1]). if_test(X) -> if X < 5, X > 1 -> X * X; X >= 5, X < 10 -> X - 2; true -> X - 3; end. % if_testをREPL環境で試す。 19> c(if_test). {ok,if_test} 20> if_test:if_test(7). 5 21> c(if_test). {ok,if_test} 22> if_test:if_test(3). 9 23> if_test:if_test(6). 4 24> if_test:if_test(-1). -4
if式の文法は他のプログラミング言語のif文と似ていますが、ちょっと違いますね。
まず、複数個の条件を書く時、ifと複数書く必要がありません。
最初にifと書いて、case式のように条件と結果を次々と書いていけばいいです。
それから、最後の条件にtrueと書きましたね。これはelseと考えればいいです。
Erlangのifにはelseがありません。なので、全ての条件に一致しない時の条件を
直接書く必要がありますが、trueと書いておけばOKです。
case 評価対象 -> パターン1 [when ガード...] -> 結果; ... パターンn [when ガード...] -> 結果 end
trueを書かないで、全ての条件に一致しない場合、例外が発生しますので、注意しましょう。
例外
Erlangで例外処理は、ちょっと形式は違いますが、Javaと同じくtry-catch式を使います。
以下の例を見ながら、try-catchを身につけましょう。
% trycatch.erl -module(trycatch). -export([go/1]). go(X) -> try test(X) of Val -> {value, Val} catch throw:exception1 -> io:format("Catch Exception_Test1~n"); throw:exception2 -> io:format("Catch Exception_Test2~n"); exit:_ -> io:format("Catch Exit~n"); error:_ -> io:format("Catch Error~n") after io:format("After~n") end . test(X) -> case X of 1 -> throw(exception1); 2 -> throw(exception2); 3 -> exit(unknown); 4 -> error(unknown) end . % trycatchをREPL環境で試す。 1> c(trycatch). {ok,trycatch} 2> trycatch:go(1). Catch Exception_Test1 After ok 3> trycatch:go(2). Catch Exception_Test2 After ok 4> trycatch:go(3). Catch Exit After ok 5> trycatch:go(4). Catch Error After ok
関数goに1から4までの数字を入れると、例外(throw)2種類、プロセス終了(exit)1種類、エラー(error)1種類を演じてくれます。
throwは呼出元が例外を処理することを期待する時、呼出元に対してエラーをなげる関数です。JavaのChecked Exceptionと似ていますね。
但し、throwでエラーを発生させても、呼出元がtry-catchでエラーを捉えなくても実行はできます。
Javaのようにコンパイルエラーは発生しません。
プログラムで捉えられなかったエラーはErlangが捉えます。その場合、スタックトレースのようなものが表示されるでしょう。
exitはプロセスを終了させたい時、呼出元にエラーをなげる関数です。try-catchでこのエラーを捉えないと、プロセスが終了してしまいます。
errorは呼出元が例外を処理できないような致命的なエラーが発生したことを知らせるため関数です。
Javaでいうと、RuntimeExceptionのようなものでしょうか。
よく見るとErlangのtry-catchはcaseと似ていることが分かります。
tryとcatchの間のコードがまさにcaseのような形式、その後、catchとafterが追加されていますね。
catchはJavaのcatchと同じで、afterはJavaのfinallyのようなものです。
以下はtry-catch式の使い方です。
try 評価対象 of パターン1 [when ガード...] -> 結果; ... パターンn [when ガード...] -> 結果 catch 例外種類1(throw, exit, error): 例外パターン1 [when ガード1] -> 結果1; ... 例外種類n(throw, exit, error): 例外パターンn [when ガードn] -> 結果n after catch結果に関係なく、返す結果 end
Erlangのtry-catchはJavaと違って、式なので戻り値があります。
今日はここまで。
次はErlangでの並行処理について書きます。
[WebSocket] [Java] WebSocketを試す
WebSocketとは
HTML5仕様を構成する要素の一つで一方向通信しかできなかった既存のHTML通信とは違って、
JavascriptのWebSocket APIとサーバサイドのWebSocket機能でクライアントとサーバ間の両方向通信を可能にする技術だ。
WebSocketの特徴
WebSocketは、通信ごとにHTTPコネクションを開く・閉じるのを繰り返すのではなく、
いったんHTTPコネクションを開いたら、コネクションを開いたままにしておいて、データをやりとりする。
コネクションが維持される間は、Ajaxのようにクライアントからサーバへの通信だけでなく、
サーバからクライアントに直接データを送ることもできる。
以下は、ChromeでWebSocketの公式サイトにあるデモを試したキャプチャである。
"Rock it with HTML5 WebSocket"という文字列を何回もSendしているのにコネクションは一つ(?encoding=text)しか発生していない。
通信の内容はFramesタブをクリックすれば、分かる。
WebSocketのAPIについて
サーバAPI
昔はサーブレットコンテナごとに違うWebSocketのAPIがあったが、
今は、JSR 356: JavaTM API for WebSocketという標準ができて、
各ベンダが自分たちのサーブレットコンテナに実装している。
標準APIがあるので、ベンダ依存のAPIは使わないこと。
WebSocketサーバプログラムはWeb(サーブレット・JSP)でもStandalone(mainメソッド)でも作れる。
JavaでWebSocketサーバプログラムを実装するために以下のアノテーションとクラスを使用する。
- @ServerEndPoint
- @OnOpen
- @OnMessage
- @OnClose
- @PathParam
- Encoder
- Decoder
- 他にも色々。
クライアントAPI
WebSocketのクライアントプログラムはどの言語でも作れるが、ここではJavascriptを取り上げる。
Javascriptでは以下のように非常に単純で分かりやすいAPIが存在する。
- WebSocket(url)
- WebSocket.send
- WebSocket.onopen
- WebSocket.onmessage
- WebSocket.onerror
- WebSocket.onclose
サンプル作成
必要なもの
- WebSocketをサポートするサブレットコンテナ(Tomcat8, GlassFish、Jetty等)
- サーバプログラム(Java、JSP)
- クライアントプログラム(Javascript)
サンプル作成スタート
時間を節約するため、サンプルは早く作りたい。
今回はNetBeansを利用してサンプル作ろう。
Netbeansをダウンロード・インストールし、立ち上げて新規プロジェクトを作成する。
カテゴリでJava Web→Webアプリケーションを選択し、次へ、プロジェクト名を入力し、次へ、
サーバーと設定画面で終了ボタンを押すと、プロジェクトが作成される。
さあ、コーディングをしてみよう。
サーバプログラム
送ったメッセージをそのまま返すだけである。
package dukelab.websocket.demo; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; /** * WebSocketデモ。 * * @author DukeLab */ // Webソケットのサーバ側クラスであること表すアノテーション。 // 引数(wsdemo)はクライアントから接続時、使われるURIを表す。 @ServerEndpoint(value = "/wsdemo") public class WebSocketDemo { @OnMessage public String onMessage(String text) { return text; } }
WebSocketのサーバプログラムは@ServerEndpointアノテーションをつけることで始まる。
@ServerEndpointの引数valueにはクライアントから接続時、使われるURIを指定する。
URIは/で始まらなければならない。
例えば、ウェブアプリケーションがlocalhost:8080で動いていてコンテキストパスがtestなら、
上記のサーバプログラムへのURLはws://localhost:8080/test/wsdemoになる。
ここでは指定していないが、@ServerEndpointアノテーションへの引数に、
メッセージ変換のためのencoder、decoderも指定できる。
本格的なアプリケーションを作る時に、encoderとdecoderが必要になりそうだ。
それから、@OnMessageアノテーションをメッセージを受信するメソッドに指定する。
メソッドの引数textがクライアントから受信したデータということだ。
onMessageメソッド内で色々処理を行って、その応答をreturnすればいい。
@OnMessageアノテーション以外に@OnOpen、@OnCloseもある。
@Onxxxアノテーションがつくメソッド(例 : onMessage(String Text))の引数や応答を返す方法は上記以外にもある。
リクエストが来た時だけでなく、サーバから自発的に送ることもできる。
それらは実装の状況に応じてドキュメントを参照すればいいだろう。
ここでは至極簡単なサンプルサーバプログラムを作った。
上記のクラスだけだ。
web.xmlとかサーブレットは要らない。
本当に便利だ。
クライアントプログラム
サンプルを実行したブラウザはChrome 33.0.1750.117である。
<!DOCTYPE html> <html> <head> <title>WebSocketDemo Client</title> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> WebSocketDemo = {}; (function(d) { function $(query) { return document.querySelector(query); } function printMessage(msg) { $("#msgbox").innerHTML += "<div>" + msg + "</div>"; } d.connect = function() { var ws = new WebSocket("ws://localhost:8080/WebApplication3/wsdemo"); ws.onmessage = function(event) { printMessage("Server : " + event.data); }; d.webSocket = ws; $("#connect").disabled = true; $("#send").disabled = false; }; d.send = function() { var msg = $("#msg").value; d.webSocket.send(msg); printMessage("Client : " + msg); }; }) (WebSocketDemo); </script> <form action="javascript:void(0);"> <input type="text" id="msg" size="20"> <input type="button" id="connect" value="Connect" onclick="WebSocketDemo.connect();"> <input type="button" id="send" value="Send" onclick="WebSocketDemo.send();" disabled> </form> <div id="msgbox" style="border-style: solid;width: 500px;height: 400px"></div> </body> </html>
Connectボタンを押すと、WebSocketオブジェクトを生成する。
WebSocketのコンストラクタにはスキームがws://又はwss://(SSLの場合)で始まるURLを指定する。
後、サーバからメッセージを受信すると、onmessageハンドラーが呼ばれる。
引数のオブジェクトのdataプロパティからメッセージの取得ができる。
Sendボタンを押すと、WebSocketオブジェクトのsendメソッドを呼び出してメッセージをサーバに送信する。
サーバより実装が簡単だ。
注意点は、IEの場合、WebSocketから使えるのはIE10からだということだ。
IE9まではWebSocketが使えない。
さらに進化したサンプル
上記までは、内部的にやり取りされるのが変わっただけで、既存のAjaxでも同じ機能のアプリケーションは作ることができる。
ここで、サーバから自発的にクライアントにメッセージを送る機能をつけてみよう。いわゆるPUSH型通信。
これはAjaxではできないのだ。
setIntervalで周期的にサーバに見に行くしかないのだ。いわゆるPULL型通信。
Ajax以外にCometというのもあるが、これはコネクション確立後、ただ、Pendingして待つだけだ。
実際の通信が送られるまで待つので、通信量を減らすことはできるが、サーバから応答が返ってくると、
また次のHTTPコネクションを確立しなければならない。
ずっと通信を確立しているWebSocketはAjaxとCometとは歴然と違うのだ。
このさらに進化したサンプルでは、制御用JSPを一個作って、そのJSPで何かを入力すると、
サーバから全てのクライアントにメッセージが送信される機能をつける。
以下は上記のWebSocketDemoクラスに機能を追加したものだ。
package dukelab.websocket.demo; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * WebSocketデモ。 * * @author dukelab */ @ServerEndpoint(value = "/wsdemo") public class WebSocketDemo { private static Set<Session> ses = new CopyOnWriteArraySet<>(); @OnOpen public void onOpen(Session session) { System.out.println("onOpen : " + session); ses.add(session); } @OnMessage public String onMessage(String text) { return "echo => " + text; } @OnClose public void onClose(Session session) { System.out.println("onClose : " + session); ses.remove(session); } public static void sendMessage(String msg) { for (Session ses : ses) { ses.getAsyncRemote().sendText(msg); } } }
要は、接続時(onOpen)にセッションをスレッドセーフなコレクションに入れておいて、
そのセッションに対して操作をすればいい。
切断時(onClose)にセッションをスレッドセーフなコレクションから削除する処理は忘れないように。
<%@page import="dukelab.websocket.demo.WebSocketDemo"%> <%@page contentType="text/html" pageEncoding="utf-8"%> <!DOCTYPE html> <% boolean isPOST = request.getMethod().toLowerCase().equals("post"); if (isPOST) { WebSocketDemo.sendMessage(request.getParameter("msg")); } %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>WebSocketDemo Control</title> </head> <body> <%= isPOST ? "Message was sent." : "" %> <form action="" method="post"> <input type="text" name="msg"> <input type="submit" name="submit" value="Send"> </form> </body> </html>
以下の画面で、左が制御用JSPで、右がエコデモである。
制御用JSPでメッセージを入力し、Sendボタンを押すと、全てのクライアントにメッセージが送信される。
これは、なかなか面白い。
Erlang 基礎ポイント2 - プログラムの形式, 無名関数, パターン照合など
Erlangプログラムの形式
REPL環境でなく、ソースファイル(erlファイル)から実行するためには
以下の形式に従う。
-module(test). -export([func1/0,func3/1,func4/3]). func1() -> func2() + 5. func2() -> 3. func3(X) -> Y = X * 2, Y * 2. func4(plus, X, Y) -> X + Y; func4(minus, X, Y) -> X - Y; func4(_, _, _) -> "nothing". % コメント
まず、module文にモジュール名を書く。
モジュール名はソースコードを含んでいるファイル名(拡張子はerl)にする。
モジュールはErlangコードの基本単位だ。
export文には外部からアクセスを許容する関数名・引数の数を記述する。
Javaにおけるpublicといえば、分かりやすい。
コンマで区切って複数指定可能。
次はJavaのようにメンバー変数が来そうだが、そんなものはない。
すぐ処理ロジックを含む関数が来る。
関数のシグネチャにリターンタイプとか、引数の型の指定がない。
関数名と引数名だけを書いて、->の後、すぐ処理内容を記述する。
関数の処理内容は行ごとにコンマで区切って、関数の最後にピリオドを打つ。
引数の数が同じ関数を複数(いわゆる、オーバーロード)書く場合、
各関数の終わりにコンマでなくセミコロンを書く。
但し、最後の関数の終わりにはコンマを書く。
型のチェックは実行時に行われる。
つまり、Erlangは動的型付け言語である。
コメントは%の後に書く。
無名関数
Erlangでは関数を無名で宣言して、変数に代入したり関数の引数や戻り値として使用することができる。
1> F = fun(A) -> A * A end. #Fun<erl_eval.6.80247286> 2> F(2). 4 3> Y = F. #Fun<erl_eval.6.80247286> 4> Y(4). 16
funで始まって、end.(ピリオドがあることに注意)で終わる。
次は無名関数を関数の引数に渡したり、戻り値として返す例だ。
% 引数 9> F = fun(A) -> A * A end. #Fun<erl_eval.6.80247286> 10> lists:map(F, [1, 2, 3, 4]). [1,4,9,16] % 戻り値 12> Plus = fun(X) -> (fun(Y) -> X + Y end) end. #Fun<erl_eval.6.80247286> 13> PlusFive = Plus(5). #Fun<erl_eval.6.80247286> 14> PlusFive(9). 14
リスト内包表記
リストから特定条件を満たす要素で構成された新しいリストを作るためのErlangの機能。
普通、リストから特定要素を持つ新しいリストを作るためには
新しい空のリストを宣言しておいて、ループと条件文を利用し、
条件にマッチした要素を空のリストに入れる処理が必要なので、
コードが長くて読みづらくなる。
しかし、リスト内包表記を使うと、リスト(例 : [ 1, 2, 3])の中で
新しいリストを作るための専用の表記ができるので、コードが短くて
読みやすい。
1> L = [1, 2, 3]. [1, 2, 3] 2> [X - 1 || X <- L]. [0, 1, 2].
後ろの部分、X <- LはリストLから要素Xを取り出すという意味で、
"||"の左側に要素Xで行う式を書く。
レコード
Cにおける構造体のようなもの。
Erlangでレコードの宣言方法は色々あるが、よく使われる方法は
拡張子がhrlのファイルにレコードを定義し、
そのhrlファイルをerlファイルで
インクルードする方法だ。
% person.hrl -record(person, { name, age, tel = 000-0000-0000 }).
% test.erl -module(test). -export([duke/0]). -include("person.hrl"). #インクルード duke() -> #person{name=duke, age=34, tel="0800000-0000"}.
レコードの宣言は、まず、-recordキーワードを書いて、
1番目の引数にレコード名、次に要素名のタプルを指定する。
要素にはデフォルト値の指定ができる。
レコードの使用は
#レコード名{要素1=値, ... , 要素x=値}
のようにする。
パターン照合
Erlang 基礎ポイント1 - DukeLabでちょっとだけ、触れたが、ここでもうちょっと説明しよう。
Erlangで、=は代入するのではなく、パターン照合するという。
以下の場合、Aに100を代入するのではなく、右側(100)の評価結果と左側のパターン(A)を照合するのだ。
A = 100
上記の例だと、ぱっとイメージしにくいだろう。
パターン照合とイメージが似ているのは正規表現ではないかな。
正規表現を使うと、ある複雑な文字列からパターンにマッチする特定の部分を手軽に抽出することができる。
ただし、正規表現のルールが分かりにくい短所はある。
同じくErlangのパターン照合も正規表現のようなものだが、
文字列だけでなく、タプル、リスト、関数の引数など、何にでも使えると考えればいいだろう。
以下の例を見てみよう。
19> T = {{name, "duke"}, {age, 34}}. {{name,"duke"},{age,34}} 20> {{_,_},{_,MyAge}} = T. {{name,"duke"},{age,34}} 21> MyAge. 34
Tにタプルが二つ({name, "duke"}と{age, 34})が入っているが、
二つ目のタプルから年齢(34)を取得したい場合、20>のように
パターン照合すればいい。20>の左側にあるパターンのうち、
_(アンダーバー)はダミー要素で何でもマッチする。
マッチするだけで変数に束縛したくない場合、_(アンダーバー)を使う。
パターンのうち、大文字で始まっているMyAgeが
二つめのタプルにある2番めの要素(34)に一致して
年齢(34)を取り出せたことが分かる。
パターン照合の基本が分かれば、後はマニュアルか
ググってパターン照合の例を参考すればいいだろう。
最後に関数のパターン照合に関する例を載せる。
% func_test.erl -module(func_test). -export([add/2]). add(X,plus) -> X + X; add(X,pow) -> X * X; add(X,Y) -> X + Y + 1. % 以下はREPL環境で実行したもの。 1> c(func_test). {ok,func_test} 2> func_test:add(3, plus). 6 3> func_test:add(3, pow). 9 4> func_test:add(3, 5). 9 5> c(func_test). {ok,func_test}
ガード
ある関数を呼び出すと、呼び出される関数を探すため、
引数に指定した値の型と関数のシグネチャとのパターン照合を行う。
Javaのオーバーロードと似ているものだと考えればよさそう。
追加的にメソッド内で引数の値の内容によって、違う動作をする必要がある時、
Javaではif文やポリモーフィズムを使うが、Erlangではガードを使えばいい。
ここでは関数を中心にガードを使い方を説明するが、
case式やif式でも使用できる。
以下のソースを見てみよう。
% guard_module.erl -module(guard_module). -export([guard_test/1]). guard_test(X) when X < 5, X > 1 -> X * X; guard_test(X) when X >= 5; X < 10 -> X - 2; guard_test(X) -> X - 2.
上記のソースを解析してみると、
呼び出された関数がguard_testで一番目の引数(X)に値が指定されていて、
Xが5未満かつ1より大きければ(コンマ[,]はandを意味)、X * Xを、
Xが5以上か10未満なら(セミコロン[;]はorを意味)、X - 2を、
それ以外の場合(つまり、elseなら)、X - 2
を返すという意味だ。
以下は上記の関数をREPL環境でテストしたものだ。
1> c(guard_module). {ok,guard_module} 2> guard_module:guard_test(5). 3 3> guard_module:guard_test(1). -1 4> guard_module:guard_test(6). 4 5> guard_module:guard_test(15). 13 6>
ガードは条件文のようなものだと考えればいい。
ガードは便利そうだが、関数の宣言部が複雑になる恐れがあるので、
なるべくパターン照合だけで済ませた方がいいと思う。
今日はここまで。
Javascript 注意点
Javascriptを使うにあたって
注意すべきところをメモっておこう。
会社、家でJavascriptプログラミング時に
経験したのを書くつもりだ。
もちろん、全て汎用的な内容だし、本に書いてある内容も
あるはずだ。
-----------------------------------------------
1)オブジェクトリテラルで最後の要素の後にコンマをつけてはならない。
=> IE7でエラーになる。
例)var o = { name : "Duke", hobby : ["movie", "animation", "reading", ""] , // ここにコンマを入れちゃだめ!!};
hobbyの次にコンマを入れるとIE7でエラーになる。
IE8とFireFox、Chromeではコンマを入れても問題ないが、
世の中未だIE6を使う人がいる現実を考えると
コンマを抜く方が精神健康上、いいだろう。
2)配列の最後の要素の後にもコンマをつけてはいけない。
いけないというか、ブラウザ間の動作が違うので避けましょうとの話だ。
今回もIEが問題だ。バージョン関係なく(正確にはIE7とIE8)。
var a = [1, 2, ];
console.log(a.length);
// 結果
// IE7 : 3
// IE8 : 3
// Chrome : 2
// FireFox : 2
IEでは最後のコンマの後をundefined要素と見なしているようだ。
ChromeかFireFoxでは本当の要素のみを配列の要素と見なしているようだ。
とにかく、結果が一つになる文法を使おう。
配列の最後の要素の後にコンマを入れちゃアカン!
3)with文は使っちゃだめ。
ある意味すごく便利なキーワードだが、間違った動作を起こす恐れがある。
(Visual BasicのWith-End Wih文と同じ感じ)
単純に参照名の省略だけでなく、内部で参照オブジェクトの有無を判別する
分岐文など、余分のコードが実行される。
コスト的にも問題だが、with文の内部がおかしくなると、バグを探すのが
非常に難しくなるので、使うのはやめた方がいい。
例えば、以下のコードは
with (obj) {
a = b;
}
実際は以下のコードと同じ処理をする。
f (obj.a === undefined) {
a = obj.b === undefined ? b : obj.b;
} else {
obj.a = obj.b === undefined ? b : obj.b;
}
出典
JavaScript: The Good Parts: Working with the Shallow Grain of JavaScript
- 作者: Douglas Crockford
- 出版社/メーカー: Yahoo Press
- 発売日: 2008/12/17
- メディア: Kindle版
- この商品を含むブログを見る
これを見ると分かるように実際どの処理が行われるかを
すく知ることはできない。
よって、with文は避けた方がいい。
4)parseInt使用する時、2番目の引数に進数を指定しなければならない。
var str = "051";
var a = parseInt(str);
console.log(a);
さあ、何が表示されるか。
51?
ブー。
41が表示される。
なぜなら、parseIntにソース文字列が何進数かを指定しないと、
文字列表現を見て自動判別するからだ。
前に0がつくと、8進数と判断する。
前に0xがつくと、16進数と判断する。
例の051は前に0がついているので8進数の51と判断し、
それを10進数の41に変換したのだ。
こういう自動判別を防ぐためには2番目の
引数に進数を指定すればいい。
var str = "051";
var a = parseInt(str, 10); // "strは10進数です"と指定する。
console.log(a);
結果は正しく51になる。
私たちが普段外からもらう数字は10進数なので、
parseIntの2番目の引数には必ず10をつけるように
しよう。
5)==, !=の代わりに===、!==を使おう。
Javascriptで==と!=は互いのオペランドの型が違う場合、
強制に型変換が行われる。
便利な面もあるが、分かりにくいバグを出してしまう場合もある。
以下の例のように想像と全然違う結果を出してしまう。
1)'' == '0' // false
2)0 == '' // true
3)0 == '0' // true
4)false == '0' // true
5)' \t\r\n ' == 0 // true
===と!==は型の変換が行われず、実際型までチェックするので
安心だ。
6)より安全なクラス宣言の仕方 --- 7)を参照すること。
Javascriptでクラス宣言する方法は色々ある。
次にPersonというJavaクラスがある。
public class Person {
private final String name;
private final int age;
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() { return name;
}
public int getAge() {
return age;
}
}
上記のPersonクラスをJavascriptで書いてみると、
方法は色々あるが、二つだけ、挙げてみる。
1番目
var Person = function(name, age) {
this.name = name;
this.age = age;
};
Person.prototype = { // publicメソッド
getName : function() {
return this.name;
},
getAge : function() {
return this.age;
}
};
Personクラスの宣言とメンバーメソッドの宣言が分かれている。
長所 :
1)インスタンス生成時、prototypeに宣言されている内容を再利用するのでメモリ管理面で有利である。
短所 :
1)宣言部が分かれているので少々分かりにくい印象がある。
2)publicメソッドでprivateメソッドが呼べない。prototypeではthisで始まるメンバーしか呼べない。
2番目
var Person = function(name, age) {
this.name = name;
this.age = age;
// private field
var count = 0;
// private method
var getCalledCount = function() {
return count++;
}
// public method
this.getName = function() {
return this.name + " : " + getCalledCount();
};
this.getAge = function() {
return this.age;
};
};
長所:
1)宣言部が統合されているので分かりやすい(変数の使用スコープが把握しやすいので)。一般的なクラス宣言に近い。
2)publicメソッド内でprivateメソッドが呼べる。
長所:
1)インスタンス生成時(つまり、new時)、毎回メソッドを動的に生成するので遅い。
答えは出た。
Javascriptはプロトタイプ型OOP言語だが、まだprototypeに慣れていない人が多いので
無理して使っても、メンテナンスする人に文句ばかり言われるだろう。
で、2番目のような宣言方式を使えば無難ではないかと思われる。
速度の問題はあるが、これ以外の問題(間違ったJavascriptの使用を意味)がもっと多いはずなので、
別に問題にならないと思う。クライアントPCの性能もよくなったし。
まず、内容が把握しやすくてメンテナンスがしやすい2番目を取ってみて、
本当に遅くなったと感じた(おそらくないと思うのだが)時、1番目を取るという方式がいいだろう。
7)クラスをnewしてオブジェクトを生成するより、オブジェクトリテラルを使う。
6)でより安全なクラス宣言の仕方を言っているが、実はこの記事全体は2年前に書いた内容で、今では好ましくないと思われる。
なぜなら、Javascriptではクラス宣言にも関数宣言にもfunctionキーワードを使うため、
クラスとして宣言しているのに間違って関数として実行してしまうおそれがあるからだ。
例えば、以下のように点を意味するPointクラスを定義したとしよう。
function Point(x,y) {
this.getX = function() {
return x;
}
this.getY = function() {
return y;
}
};
これは普通に以下のようにnewキーワードをつけて呼び出すと、問題なくオブジェクトを生成できるが、
var p = new Point(10, 15);
p.getX();
10
以下のようにnewをつけないと、グローバルオブジェクトを汚してしまう。
Point(10,15);
window.getX();
10
簡単な例だが、大規模なプロジェクトでは、探しにくいバグの元になるだろう。
なので、代わりにオブジェクトリテラルを使う方がいい。
例えば、上記のPointは以下のようにオブジェクトリテラルを使える。
function createPoint(x, y) {
return {
getX : function() {
return x;
},
getY : function() {
return y;
}
};
}
var p = createPoint(10, 15);
p.getX();
10
newをつける必要もなく、グローバルオブジェクトを汚すこともない。
Object.createを使うと、継承もできる。
なので、prototype方式を使ったクラス宣言も使う必要がなさそう。
Javascriptでオブジェクト生成はオブジェクトリテラルに限ると思う。
Oracle 12CのPluggable Database
DBにも仮想化が到来した。
Oracle 12CのPluggable Database機能を利用すると、
一つの物理マシンだけで、複数の仮想DBをコンテナDBというものに
まとめて管理できるそうだ。
詳しくは以下の記事を参照
http://www.publickey1.jp/blog/12/oracle_database_12c.html
仮想化は
Oracleのような大型アプリケーションから小さなクラスまで
応用できるものが多く、よくできた概念だと思う。