2010年2月25日木曜日

Android上でPython、Lua、JavaScriptなどを実行するスクリプティング環境が凄い

このブログ記事をはてなブックマークに追加

ASE (Android Scripting Environment)を使って簡単にPython, Perl, JRuby, Lua, BeanShell, JavaScript (Rhino), それにシェル(将来的にはさらにたくさんのスクリプト言語)をAndroid上で実行できるのはご存じだろうか。ASEのインストールからスクリプトの作成、実行まで、すべてAndroid単体でできる。もちろん、PC上でコーディングしたい場合は、USBで繋げてPC上のスクリプトをAndroid端末上で実行することもできるし、PC上のコードをAndroid端末にコピーすることもコマンド一発だ。さらに、各種センサー、位置情報、SMS、テキストの読み上げなどもスクリプト上で操作できるというのだからこれを使わない手はない。

そこで、試しにPythonスクリプトを書いてみた。Android端末のGPS機能で緯度経度を取得して、逆ジオコーディングで現在の住所を表示するスクリプトだ。Google Maps APIのURLなどを含めた雛形だけPCからAndroid端末(HT-03A)にコピーして、通勤時の電車の中で書いたのが以下のコードだ。

# -*- coding: utf-8 -*- import android, time, json, urllib url = "http://maps.google.com/maps/geo?ll=%.16f,%.16f&sensor=false&output=json&hl=ja&oe=UTF8&key=Google_Maps_API_Key" droid = android.Android() droid.startLocating() for i in range(10): location = droid.readLocation() if location["result"]: break time.sleep(1) lat, lng = location["result"]["network"]["latitude"], location["result"]["network"]["longitude"] geo_data = json.loads(urllib.urlopen(url % (lat, lng)).read())["Placemark"] geo_data = sorted(geo_data, key=lambda x: -x["AddressDetails"]["Accuracy"]) address = geo_data[0]["address"] droid.makeToast(address)

シェル環境が使えるので、print文を使ったデバッグも簡単だった。苦労すると思われたソフトキーボードもQWERTY配列と予測変換が使えるので思ったよりも楽だったし、トラックボールでのカーソル移動、コピー、カット、ペーストもある。できればキーボード付き端末が欲しいところだが、これからAndroid端末はたくさん出てくると予想されるので、そちらに期待している。早いところ、DroidXperia X10 mini proが日本で出て欲しいところだ。因みに、作成したスクリプトはASE上でも実行できるし、ホーム画面にショートカットアイコンを作って直接実行もできる。

本格的なAndroidアプリケーションを作成するにはAndroid SDKとJavaになるのだろうが、個人的に使う小さなスクリプトならASEの方が端末上でサクッと書けるし、比較的大きなコードでもPC上で作成し、PCに端末を接続してデバッグ、完成したら端末にコピーすればいい。それに、様々なスクリプト言語が使えるので、自分が使いたいスクリプトを選べるのも魅力だ。対話環境も利用できるのでスクリプト言語の勉強のために使うのもありだと思う。

以下に、ASEの導入方法と使い方を書いておく。

準備

ASEはGoogleの公式プロジェクトではあるが、ちゃんと認証を受けたソフトウェアではないので(将来的にはAndroid Marketで配布されるらしい)、Androidの設定で[アプリケーション]-[提供元不明のアプリ]のチェックをオンにしておく必要がある(インストール後にはオフに戻しておくこと)。次に、Android端末でandroid-scriptingのFeatured downloadsにある最新版をダウンロードして、そのままインストールする。

ASEを起動し、メニューを開くと[Interpreters]の項目があるので、それを選択する。初期状態ではShellだけが選択できるようになっているが、他のスクリプト言語を入れたい場合は、メニューを開いて[Add]を選択する。ダイアログが開き、複数のスクリプト言語が表示されるので、使いたい言語を選択する。選択すると自動的にダウンロードされインストールされる。

PCとAndroid端末を連動させたい場合は、PCにAndroid SDKをインストールする必要がある。以前に導入方法を書いたので、そちらを参考にして欲しい。

さて、これでASEを使用する準備が整った。

使い方

まず、端末上でのコーディング方法を説明する。ASEを起動してメニューを開き、[Add]を選択するとダイアログが開くので、作成したいスクリプト言語を選ぶ。ソースコードの雛形が出るので、それにコードを書いていく。書き上がったら適当なファイル名で保存する。実行するにはASEを起動した最初の画面で表示されるスクリプトファイルを選択してもいいし、スクリプトのショートカットを作っておいてホーム画面から起動してもいい。因みにショートカットを作成するには、ASE起動時に表示されるスクリプトファイルを長押しして[Add Shortcut]を選べばいい。

次に、PCとAndroid端末をUSBで接続してAndroid上で実行する方法を示す。これについては、Android ASE (Android Scripting Environment)入門を参考にさせてもらった。

最初にPCとAndroid端末をUSBで接続する。このとき端末側の設定で[アプリケーション]-[開発]-[USBデバッグ]をオンにしておく。

ASEのインタプリタ起動時に表示されるAP_PORTが55063で、PC上のポートを4321(任意の未使用ポートを選べる)とする場合は、Windowsでは以下のようにコマンドプロンプト(シェル)上で設定する。因みに%はプロンプトを示している。

% adb forward tcp:4321 tcp:55063 * daemon not running. starting it now * * daemon started successfully * % set AP_PORT=4321

最後にデーモンを停止してポートを閉じる場合は、以下のコマンドを使う。

% adb kill-server

また、PCにもAndroidで使用するスクリプト環境が入っている必要がある。例えばPythonを使う場合はあらかじめPCにもPyhton 2.6をインストールしておき、さらにandroid.pyを、PythonをインストールしたディレクトリのLib/site-packages/にコピーしておく。これでPC上で実行したスクリプトがAndroid端末で動作する。

最後に、PC上で作成したスクリプトをAndroid端末にコピーする方法だが以下のようにすればコピーできる。ただし、将来にわたってコピー先のディレクトリが不変である保証はない。

% adb push 作成したスクリプトファイル /sdcard/ase/scripts/

Android端末上のディレクトリを調べたい場合、

% adb shell

とすればリモートシェルが立ち上がるので、以下のように調べることができる。

$ cd /sdcard/ase/scripts cd /sdcard/ase/scripts $ ls ls saychat.py notify_weather.py speak.py weather.py saytime.py sayweather.py test.py $

注意事項

今回、HT-03Aで使用したのだが、入力方法がiWnn IMEやOpenWnnになっているとASEのインタプリタ上で文字を入力できなかった。入力するには入力方法をAndroidキーボードにする必要がある。ただし、スクリプトの作成・編集についてはどの入力方法でも問題はなかった。

追記(2010/4/5):

記事を書いた時点でのASEはase_r16.apkだったが、現在の最新版はase_r20.apkになっている。頻繁に更新されるのでダウンロードする際は最新版の確認をして欲しい。

追記(2010/6/27):

ASE r22からreadLocation()のデータ構造が変更されたようなので、それに合わせて上述のコードを修正した。

続きを読む...

2010年2月15日月曜日

「ハッカーと画家」を読んで

このブログ記事をはてなブックマークに追加

ポール・グレアムの「ハッカーと画家 コンピュータ時代の創造者たち」を読んだ。もっと以前に読んでいてしかるべき本であったが、読みそびれていた。もっと技術的な話だと思っていたのだが、(プログラマであれば)気軽に読めるとても面白いエッセイ集だった。

個人的に興味深かったのは、第4章の「天邪鬼の価値」で、ジョブズとウォズニアックが錠開け装置をいじっている写真だ。リチャード・ファインマンがマンハッタン計画で重要書類の入っている金庫を破ることを楽しみにしていたり、グレアムが学生の頃のMITではピッキングが流行ったりしていたそうだが、それを読んで、自分が子供の頃のことを思い出した。古いダイアル式南京錠が家にあったのだが、それは錠が閉まっていて親に聞いても番号が分からないという。そこでどうやったら開けることが出来るかを考えるうちに面白くなり、次第に夢中になっていった。ダイアルは3桁の数字なので、10*10*10=1000通りを試したら必ず開けられる。しかし、そんな面倒なことはしたく無い。悪用されても困るので詳しくは書かないけど、しばらくいじっているうちにキーの動きに法則があることに気がついた。こうなれば簡単で、最大で10+10+10=30通りを試すだけでよくなる。これで開かない錠前を開けることができた。今で言えばセキュリティに欠陥があるということなのだろうが、困難だと思われることに挑戦し、それを克服することは、とても強い達成感となる。子供ながらにそう感じた。ファインマンもMITの学生もそう感じたのだろう。これがハックする理由の原点だろうか。

第13章の「オタク野郎の復讐」を読めば分かるけど、ポール・グレアムは熱心なLispハッカーで、Lispを非常に高く評価している。確かに他の言語の意義についても説いてはいるけど、「他のプログラミング言語はとうとう(Lispの生まれた)1958年に追いつこうとしている」と書いていることからもLispが最も良い言語だと考えているのが分かる。確かにLispは良い言語だと思うけど、個人的には万人にLispを勧めることはできないなぁ。

自分の場合は、普段はC++とPythonを利用していて(多くの場合はこれで事足りる)、最近は並列化のためにTBBやCUDA、それとErlangを使ったりしている。もちろんAndroidで開発するときはJavaだし、グラフィカルなことをしたいときはProcessingやActionScriptを使うし、他にも様々な言語をいじったりするけども、結局、言語の選択はその環境と目的に強く依存されると思う。それに、複数の言語から選択できる方が一つの言語に固執するより健全じゃないかなぁ(一つの言語を極めてみるというのには異存はないけど)。複数の言語を覚えるのは大変そうに思えるかもしれないけど、最初に2~3の言語を習得すれば、それ以降はあまり苦労せずに覚えることができると思う。

ところでLispの開発環境についてだけど、WindowsでCommon Lispを使いたいときはEmacs Lispの代わりにCommon Lispライクなマクロ言語を搭載したxyzzyが手軽だ。LinuxならCLISPSBCLがよく使われている。

ポール・グレアムの最新のエッセイはPaul Graham - Essaysから読むことができる。日本語訳はnaoya_t:ポール・グレアムのエッセイと和訳一覧でまとめられているので、興味のある方は読んでみてはどうだろうか。あと、第13章「オタク野郎の復讐」で納得しかねたPythonユーザはPaul Prescodの「PythonとLispの関係について」(川合史朗訳)を読んでみることをお勧めしたい。

続きを読む...

2010年2月5日金曜日

Erlang基礎文法最速マスター

このブログ記事をはてなブックマークに追加

先日、Lua基礎文法最速マスターを書いてみたが、予想以上に自分自身への学習効果が高かった。そこで、普段使っているPythonと同じぐらいに使いこなしたいと思っていたErlang(アーラン)の基礎文法最速マスターを書いてみることにした。

Erlangが関数型プログラミング言語であることもあり、書き下すのは思ったよりも大変だったが、学習効果はかなりあったと思う。言語を習得したいときはこのようなまとめを書いてみるのが良さそうだ。

ただ、手続き型プログラミング言語に比べて異なる部分があまりにも大きいので、すべてを説明することは難しく、これを読んだだけですぐに使えたりはしないかもしれない。また、間違っていたり、足りない部分などがあったら、教えて頂けるとありがたい。



1. 基礎

対話環境

コマンドラインからerlを実行すると対話環境(シェル)になります。コマンドの最後にはピリオド(.)が必要です。help()で簡単な説明を読むことができます。

$ erl 1> A = 10. % Aに10を束縛. 10 2> B = A * 5. % BにA * 5 (50)を束縛. 50 3> hello. % helloはアトム(定数のようなもの/後述). hello 4> b(). % 現在の束縛変数を表示. A = 10 B = 50 ok 5> A = 20. % 変数は一度しか代入(束縛)できない. ** exception error: no match of right hand side value 20 6> f(). % 束縛変数をすべてクリアする. ok 7> A = 20. % 再度束縛できる. 20 8> q(). % Erlangシェルを終了させる. ok

画面出力

io:formatで書式指定の出力を行うことができます。

~p : 整形して出力. ~s : 文字列を出力. ~f : 浮動小数点数を出力. ~w : Erlangの標準構文として出力(アトムならそのまま、文字列ならリストとして出力など) ~n : 改行.

io:format("Hello world~n"). % "Hello world" %% "5 , 1.235, 30, erlang" io:format("~-5w, ~6.3f, ~p, ~s~n", [5, 1.23456, 30, "erlang"]).

コメント

%から行末までがコメントになります。ブロックコメントはありません。慣例として%%がよく使われます。

% コメント. %% コメント.

変数の代入

Erlangの変数の先頭は必ずアルファベットの大文字かアンダーバー(_)になります。また、一度代入すると後から変更できません。代入した変数のことを束縛変数と呼びます。まだ代入されていない変数のことを未束縛変数と呼びます。変数は動的型付けなので宣言は必要ありません。

A = 5. % ※1 B = "Hello world". C = atom. A = 10. % Aは束縛変数のためエラーになる. A = 5. % ※2 Aは束縛変数だが、パターン照合が一致するのでエラーにならない.

実のところ、=は代入演算子ではなく、パターン照合演算子になります。例えば、※1のA = 5のAは未束縛変数であり、どんな値にもパターンが一致するので5の照合が成功して、Aに5が束縛できるのです。※2のA = 5では、既にAは5に束縛されているので、数値の5か、5の値を持つ変数とだけ照合が成功します。

Erlangで重要な要素の一つにアトムがあります。アトムとは数値以外の不変値を表すのに使用します。C言語の列挙子などが近い存在です。アトムは通常アルファベットの小文字から始まります。変数に代入することができます。

atom. A = hello.

また、先頭文字を大文字にしたり、空白や特殊記号を使用したい場合はシングルクォーテーション(')で囲むことでアトムとなります。

'Atom'. 'This is an atom.'.

ある項目を一つにまとめたい場合はタプルを使います。タプルは中括弧({})で括ることにより作れます。

A = {pen, book, desk}. B = {b, {language, "erlang"}, 10}.

タプルから値を取り出す場合、パターン照合を使います。

C = A. % 未束縛変数Cに {pen, book, desk}が入る. {b, D, 10} = B. % Bのタプルと未束縛変数Dを含む{b, D, 10}はパターンが一致するのでDに{language, "erlang"}が入る. {b, E, 20} = B. % パターンが一致しないのでエラーになり、Eは未束縛変数のまま.

いくつかのパターン照合の例を以下に挙げておきます。

{X, 123} = {10, 123}. % 成功. Xに10が入る. {X, 12} = {10, 123}. % 失敗. 12と123が一致しない. {A, B, A} = {5, 10, 5}. % 成功. Aに5、Bに10が入る. {A, B, C} = {5, 20, 8}. % 失敗. Bには10が束縛されている. {A, _, _} = {5, 20, 8}. % 成功. アンダーバー(_)はどの値とも一致する. [H|T] = [1, 2, 3]. % 成功. Hに1、Tに[2, 3]が入る. [A, B|T] = [1, 2, 3, 4, 5]. % 成功. Aに1、Bに2、Tに[3, 4, 5]が入る.

スクリプトの実行

コンパイルして実行するには以下のようにコマンドを実行します。ただし、ソースコードをコンパイルするためには、先頭でモジュール名をアトムで指定する必要があり、モジュール名は基本的に拡張子を除いたファイル名が使われます。例えば、ソースコードのファイル名がtest.erlであれば、-module(test).と指定します。-exportについては後述の関数の説明を参照してください。

test.erl

-module(test). -export([hello/0]). hello() -> io:format("Hello world~n").

シェル上で実行:

1> c(test). % コンパイル. {ok,test} 2> test:hello(). Hello world ok

コマンドラインで実行:

$ erlc test.erl % コンパイル. $ erl -noshell -s test hello -s init stop Hello world

ここでは、-noshellにより対話型シェルを立ち上げず、-s test helloでtest:hello()を実行し、-s init stopで終了させています。

次に示すように、escriptで実行すればコンパイルは不要ですが、実行時に呼ばれる関数はmain/1となります。-moduleと-exportは必要ありません。

test.erl

#!/usr/bin/env escript main(_) -> io:format("Hello world~n").

実行:

$ ./test.erl Hello world

コマンドライン引数を取る場合は、以下のようにします。

test.erl

-module(test). -export([main/0, main/1]). main() -> % 引数なしの場合. io:format("No argument.~n"). main(A) -> % 引数ありの場合. [io:format("~s ", [X]) || X <- A], io:nl().

実行:

$ erlc test.erl % コンパイル. $ erl -noshell -s test main Hello world -s init stop Hello world $ erl -noshell -s test main -s init stop No argument.

2. 数値

数値の表現

Erlangは最後がピリオド(.)になるので、1.は整数の1であり、実数の1.0ではありません。実数の1.0にするには、1.0.と書く必要があります。

A = 1. % これは整数の1 B = 1.0. % これは実数の1.0

各種演算

5 + 2. % 7 5 - 2. % 3 5 * 2. % 10 5 / 2. % 2.5 5 div 2. % 2 (商) 5 rem 2. % 1 (剰余)

インクリメントとデクリメント

Erlangは関数型言語で代入は一度しかできないので当然インクリメントはできません。

3. リスト

Erlangでは角括弧によりリストを作ることができます。また、単一代入なので一度作成したリストを変更することはできません。変更したい場合は別のリストを作成します。

リストの作成

A = [1, 2, 3].

リストの要素への参照

Erlangではリストの先頭のインデックスは1となります。

%% 要素の参照. lists:nth(1, A). % リストAの先頭の要素. hd(A)と同じ. lists:nth(2, A). % リストAの2番目の要素.

リストの個数

N = length([1, 2, 3]). % 3

リストの操作

A = [1, 2, 3]. %% 先頭を取り出す. F = hd(A). % Fは 1 [H|T] = A. % Hは 1、Tは[2, 3] %% 先頭に追加. B = [5|T]. % Bは[5, 2, 3] %% 末尾を取り出す. C = lists:last(B) % Cは3 [L|R] = lists:reverse(B). % Lは3、Rは[2, 5] %% 末尾に追加. D = A ++ [9]. % Dは[1, 2, 3, 9]

4. 文字列

文字列表現

ダブルクォーテーション(")で括ると文字列になりますが、Erlangではそれを整数のリストとして扱っています。

"abc". % [97, 98, 99] と同じ意味. io:format("~p~n", [[97, 98, 99]]). % "abc"と表示される. A = "abc". % [97, 98, 99]

文字列操作

%% 結合. A = string:concat("aaa", "bbb"). % "aaabbb" A = lists:concat(["aaa", "bbb", "ccc"]). % "aaabbbccc" join([]) -> []; join([H|T]) -> string:concat(H, join(T)). % バイナリ文字列の場合は、Hをbitstring_to_list(H)に. A = join(["aaa", "bbb", "ccc"]). % "aaabbbccc" %% 分割. A = re:split("aaa, bbb, ccc", "\s*,\s*"). % [<<"aaa">>, <<"bbb">>, <<"ccc">>] バイナリ文字列. A = regexp:split("aaa, bbb, ccc", "\s*,\s*"). % ["aaa", "bbb", "ccc"] regexpは遅いのでre推奨. %% 長さ. L = length("abc"). % 3 %% 切り出し. S = string.substr("abcd", 1, 2). % "ab" 文字列の先頭は1、先頭から2文字を切り出し. %% 検索. R = string:rstr("abcd", "cd"). % 見つかった場合はその位置(この場合は3)、見つからなかった場合は0が返る.

比較演算子

整数と浮動小数点数の比較以外では==と/=の代わりに、=:=と=/=を使うようにしましょう。

N1 == N2 % N1はN2と等しい. N1 =:= N2 % N1はN2と同一. N1 /= N2 % N1はN2と等しくない. N1 =/= N2 % N1はN2と同一でない. N1 < N2 % N1はN2より小さい. N1 > N2 % N1はN2より大きい. N1 =< N2 % N1はN2以下. N1 >= N2 % N1はN2以上.

5. 関数

Erlangでは関数名は必ずアルファベットの小文字から始まり、以下のような構文になります。

関数名(引数) [when ガード] -> 式1, 式2, ... 式N.

式はカンマ(,)で区切り、関数の終端はピリオド(.)になります。また、モジュールの外部から関数を呼び出すためには-exportアノテーションで関数を指定しなければなりません。例えばmain([A])を呼び出したい場合、-export([main/1]).のように指定します。これは引数が1つあるmain関数をエクスポートするという意味です。Erlangでは引数の個数のことをアリティと呼びます。因みに、-exportで関数を指定しなくても、-compile(export_all).とすれば、モジュール内の関数すべてを外部から呼び出せます。

以下のsum関数は再帰を使ってリスト内のすべての要素を足し合わせます。ここでは引数のパターンが[]と[H|T]の2種類出てきていますが、上から順にパターンが照合され一致する関数が評価されます。それぞれの関数の区切りにはセミコロン(;)が使用されます。

sum([]) -> 0; sum([H|T]) -> H + sum(T).

関数名の右側にあるwhenとはガードと呼ばれるパターンを補助する仕掛けで、このガードの条件を満たさなければパターンが一致していても式は評価されません。以下のsum_odd関数はカードを使って奇数のみを足し合わせています。

sum_odd([]) -> 0; sum_odd([H|T]) when H rem 2 =:= 1 -> H + sum_odd(T); sum_odd([_|T]) -> sum_odd(T).

サンプルプログラムとして、ガードを使ってFizzBuzz問題を解いてみます。1から100までを表示する場合はfizzbuzz(100).と実行します。

fizzbuzz(N) when N rem 15 =:= 0 -> io:format("FizzBuzz~n"); fizzbuzz(N) when N rem 3 =:= 0 -> io:format("Fizz~n"); fizzbuzz(N) when N rem 5 =:= 0 -> io:format("Buzz~n"); fizzbuzz(N) -> io:format("~p~n", [N]).

パターン照合を使って以下のように書くこともできます。

fb(_, 0, 0) -> "FizzBuzz"; fb(_, 0, _) -> "Fizz"; fb(_, _, 0) -> "Buzz"; fb(N, _, _) -> N. fizzbuzz(0) -> ok; fizzbuzz(N) -> fizzbuzz(N-1), io:format("~p~n", [fb(N, N rem 3, N rem 5)]).

Erlangでは、funを使うことによって無名関数を使うことができます。

F = fun(X) -> X * 3 end. F(10). % 30

6. 制御文

case文

case 条件 of パターン1 [when ガード1] -> 式1; パターン2 [when ガード2] -> 式2; ... パターンN [when ガードN] ->式N end

パターンを1から順に条件と照合して一致するパターンの式を返します。case文でもガードを使うことができます。

以下に例を示します。

X = case {20, 30, 40} of {10, A, B} -> one; {20, A, B} when A > 50 -> two; {20, A, _} when A < 50 -> three; true -> others end.

タプル{20, 30, 40}とパターンを比べると、2番目のパターン{20, A, B}と一致してますが、ガードのA > 50の条件に一致しないので式は評価されません。次のパターン{20, A, _}も一致していて、さらにガードA < 50の条件とも一致するので式が評価され、アトムthreeがXに入ることになります。最後のパターンtrueはすべての条件と一致します。

上述のFizzBuzz問題をcase文とリスト内包表記(後述)を使って1行で書くこともできます。

[io:format("~p~n",[case{N,N rem 3,N rem 5}of{_,0,0}->fizzbuzz;{_,0,_}->fizz;{_,_,0}->buzz;{N,_,_}->N end])||N<-lists:seq(1,100)].

if 文

if ガード1 -> 式1; ガード2 -> 式2; ... ガードN -> 式N end

例を挙げます。

N = 10. %% Resultにはsameが入る. Result = if N < 10 -> low; N =:= 10 -> same; N > 10 -> high end.

for/while文

Erlangにはfor文もwhile文もありません。その代わりに関数を使って表現できます。

例えばC言語のfor文を使った以下のようなコードについて考えます。

for (i = 1; i <= 10; i++) { printf("%d\n", i * i); }

Erlangではこれを以下のように書くことができます。

for(M, M, F) -> [F(M)]; for(I, M, F) -> [F(I) | for(I+1, M, F)]. for(1, 10, fun(I) -> io:format("~p~n", [I*I]) end).

7. ファイル入出力

ファイル操作にはfileモジュールを利用します。

{ok, S} = file:open("input.txt", read). % 入力をinput.txtから読み込む. {ok, W} = file:open("output.txt", write). % 出力をoutput.txtに書き出す. %% input.txtから数値を読み込んで、その数値を倍にしてoutput.txtに書き込む. for_write(eof, _, _) -> ok; for_write(L, S, W) -> {N, _} = string:to_integer(L), io:format(W, "~p~n", [N*2]), for_write(io:get_line(S, ''), S, W). for_write(io:get_line(S, ''), S, W).

バイナリデータinput.datを読み込んで、それをoutput.datに2回書き込みます。

{ok, B} = file:read_file("input.dat"). file:write_file("output.dat", B). file:write_file("output.dat", B, [binary, append]).

8. 並行プログラミング

my_server.erlに以下のコードを作成します。

-module(my_server). -export([loop/0]). loop() -> receive hello -> io:format("Hello!~n"), loop(); bye -> io:format("See you!~n") end.

これをシェル上でspawn関数を使って、spawn(fun モジュール名:関数名/アリティ)のように呼び出します。因みにfun モジュール名:関数名/アリティは、関数の参照を行うための記法になります。

1> Pid = spawn(fun my_server:loop/0). 2> Pid ! hello. Hello! 3> Pid ! bye. See you!

spawn関数によりmy_server:loop関数をプロセス上に実行させて、プロセスIDのPidを取得し、送信演算子 ! でhelloメッセージを送ることによりプログラムから返事が返ってきます。上記のプログラムではbyeと送ることで終了します。

spawn関数は上記の方法のほかに、spawn(モジュール名, 関数名, [引数])のように使うこともできます。

1> Pid = spawn(my_server, loop, []).

並行プログラミングはErlangの存在を際立たせているのですが、ここで扱うには規模が大きすぎるので割愛します。詳しくは参考サイトなどを参照してください。

知っておいた方がよい文法

真偽値

真偽値の型はありませんが、アトムのtrueとfalseが真偽値のリテラルとして使われます。

not true. % falseアトムが返される.

論理演算子

true or false. % true true and false. % false not true. % false

ビット演算子

1 bor 2. % 3 5 band 3. % 1 5 bxor 3. % 6 bnot 3. % -4 1 bsl 2. % 4 - 左シフト 16 bsr 2. % 4 - 右シフト

map関数

%% [1, 4, 9, 16 ,25] - リストの要素をそれぞれ2乗にする. lists:map(fun(X) -> X * X end, [1, 2, 3, 4, 5]).

定義された関数を使う場合は以下のようになります。

square(X) -> X * X. % 2乗を返す関数. lists:map(fun square/1, [1, 2, 3, 4, 5]).

filter関数

%% [1, 3, 5] - 奇数のみのリストを作る. lists:filter(fun(X) -> X rem 2 =:= 1 end, [1, 2, 3, 4, 5]).

リスト内包表記

上述のmap関数はリスト内包表記を使って以下のように書くことができます。

[X * X || X <- [1, 2, 3, 4, 5]]. % [1, 4, 9, 16, 25]

上述のfilter関数は以下のように書くことができます。

[X || X <- [1, 2, 3, 4, 5], X rem 2 =:= 1]. % [1, 3, 5]

数値と文字列の変換

list_to_integer("12"). % 12 list_to_float("123.5"). % 123.5 integer_to_list(12). % "12" float_to_list(123.5). % "1.23500000000000000000e+002"

参考サイト


基礎文法最速マスターまとめサイト


続きを読む...

2010年2月2日火曜日

Lua基礎文法最速マスター

このブログ記事をはてなブックマークに追加

最近、基礎文法最速マスターというプログラミング言語の解説が流行ってるようなので、便乗してみた。個人的にはC++やPythonの方が慣れ親しんでいるのだが、自分でも勉強できるように普段使っていない言語を書いてみることにした。以前にここのブログで言及した言語、Processing、Erlang、Lua、PowerShellなどの中でもErlangとLuaに興味があったので、比較的書きやすいLuaを選んでみた。

何故Luaなのか? Wikipediaによると、Luaはブラジル・リオデジャネイロのカトリカ大学で生まれた手続き型言語だ。高速な動作、高い移植性、組み込みの容易さが特徴だ。また、ホストプログラムへの組み込みが容易であることもあって、コンピュータゲームなどで利用されている。有名どころでは、PlayStation HomeWorld of Warcraftなどがある。また、小飼弾氏もなんてめんこい言語と述べているように評価が高い。

さて、早速Lua基礎文法最速マスターを書いてみる。上述のように自分にとっては勉強を兼ねて書いているので、間違いや記述漏れなどがあるかもしれない。コメントなどで指摘していただけるとありがたい。また、C言語の連携など、足りない部分については随時追加していく予定だ。

追記(2010/2/9): 「C言語とLuaの連携」を追加した。



1. 基礎

対話環境

Luaは対話環境として実行できます。

$ lua Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio > print("Hello world") Hello, world > print(1 + 2) 3 > print(type("Hello world")) string

print関数の代わりに = を利用することもできます。

> = "Hello world" Hello world > = 1 + 2 3 > = type("Hello world") string

print関数

print("Hello world") print(1 + 2)

コメント

ハイフン2つ(--)から行末までがコメントになります。

-- コメント.

複数行コメントには--[[ ~ ]]を使います。また、最後の]]を--]]としておけば、先頭の--[[を---[[と変更することでコメントを外すことができます。

--[[ これは, コメントです. --]] ---[[ print("これは,") print("コメントではありません.") --]]

変数の宣言と代入

大域変数には宣言はありません。局所変数にはlocalを使います。

a = 1 a = "hello" local a = 3 local a = "world"

Luaでは多重代入することができます。

a, b, c = 1, 2, 3

多重代入を利用してスワップが可能です。

a, b = b, a -- aとbの値を入れ替える.

スクリプトの実行

コマンドラインから以下のように実行することができます。

lua test.lua

またコマンドライン引数はテーブルargに保持されます。

--[[ arg[-1]: "lua" arg[0]: "test.lua" arg[1]: "arg1" arg[2]: "arg2" --]] lua test.lua arg1 arg2

2. 数値

数値の表現

実数と整数の区別はありません。以下の変数b, c, dは同じ数値になります。

a = 1 b = 1.234 c = 0.1234e1 d = 12.34e-1

各種演算

num = 5 + 2 -- 7 num = 5 - 2 -- 3 num = 5 * 2 -- 10 num = 5 / 2 -- 2.5 num = 5 % 2 -- 1 (剰余) num = 5 ^ 2 -- 25 (累乗)

インクリメントとデクリメント

Luaには++演算子や+=演算子がないので以下のように記述します。

i = i + 1 i = i - 1

3. 文字列

文字列表現

文字列はシングルクォート(')かダブルクォート(")で囲みます。2重の角括弧([[~]])でも文字列となり、エスケープシーケンスなどもそのまま表示できます。

str = 'abc' str = "abc" str = [[abc]] str = 'ab"cd"ef' -- ab"cd"ef str = "ab'cd'ef" -- ab'cd'ef str = "abc\ndef" -- abcに改行が入り、次の行にdefが出力される str = [[abc\def]] -- abc\defが1行で出力される

string.format関数を利用してC言語のprintf関数のような書式設定ができます。

string.format("%d, %6.3f", 10, 12.34567) -- 10, 12.346 ("%d, %6.3f"):format(10, 12.34567)

"%q"は、安全に読み出せる文字列に書式化します。

--[[ "test\ \"test\"\ test" --]] string.format("%q", 'test\n"test"\ntest') ("%q"):format('test\n"test"\ntest')

文字列操作

分割については標準ライブラリにないので自前でsplit関数を用意します。

-- 結合. a = "aaa" .. "bbb" -- "aaabbb" a = table.concat({ "aaa", "bbb", "ccc" }, ",") -- "aaa,bbb,ccc" -- 分割. function split(str, del) p, nrep = str:gsub("%s*"..del.."%s*", "") return { str:match((("%s*(.-)%s*"..del.."%s*"):rep(nrep).."(.*)")) } end split("aaa, bbb, ccc", ",") -- { "aaa", "bbb", "ccc" } -- 長さ. length = #"abc" -- 3 -- 切り出し. substr = string.sub("abcd", 0, 2) -- ab substr = ("abcd"):sub(0, 2) -- 検索. result = string.find("abcd", "cd") -- 見つかった場合はその位置(この場合は3)、見つからなかった場合はnilが返る. result = ("abcd"):find("cd")

4. 配列

配列変数の宣言と代入

-- 配列の宣言 array = {} -- 配列への代入 array = { 1, 2, 3 }

配列の要素の参照と代入

Luaでは配列の添え字(インデックス)は通常1から始まります。

-- 要素の参照. array[1] -- 配列arrayの先頭. array[2] -- 要素の代入. array[1] = 1 array[2] = 2

配列の個数

array_num = #array array_num = table.getn(array)

配列の操作

array = { 1, 2, 3 } -- 先頭を取り出す. first = table.remove(array, 1) -- firstは 1, arrayは { 2, 3 } -- 先頭に追加. table.insert(array, 1, 5) -- arrayは { 5, 2, 3 } -- 末尾を取り出す. last = table.remove(array) -- lastは 3, arrayは { 5, 2 } -- 末尾に追加. table.insert(array, 9) -- arrayは { 5, 2, 9 }

5. テーブル

Luaのテーブルは連想配列になっていて、nilを除く数値や文字列を添え字とすることができます。上述の配列についても基本的にはテーブルとなります。また、テーブルを入れ子にしたり、関数を保持させることもできます。

テーブルの宣言と代入

t = { 1, 2, 3 } t = { name = "lua", length = 3, sense = "moon" } t = { "students", name = { "taro", "hanako", "kenta" }, num = 3 } t = { func1 = function() return "ok" end, func2 = function(a, b) local c = a * b return c end }

テーブルの要素の参照と代入

-- 要素の参照. t[1] t["name"] t.name -- 文字列を添え字にする場合はピリオド(.)を使用できる. t.func2(2, 3) -- テーブルに保持した関数を呼び出す. -- 要素の代入. t[1] = 5 t["name"] = "lua" t.name = "lua"

6. 制御文

if 文

if 条件 then -- 条件が真. end

if ~ else文

if 条件 then -- 条件が真. else -- 条件が偽. end

if ~ elseif 文

if 条件1 then -- 条件1が真. elseif 条件2 then -- 条件1が偽、条件2が真. end

while文

while 条件 do -- 条件が真の間、処理される. end

repeat ~ until文

repeat -- 条件が偽の間、処理される. until 条件

for文

for i = 1, 10 do print(i) -- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10を出力. end for i = 1, 10, 2 do print(i) -- 1, 3, 5, 7, 9を出力. end

汎用forループとipairs/pairs関数を使って配列/テーブルを出力できます。

for i, value in ipairs({ 10, 20, 30 }) do --[[ 1 10 2 20 3 30 と表示される. --]] print(i, value) end data = { a = 5, b = 6, c = 7 } for key, value in pairs(data) do --[[ a 5 c 7 b 6 と表示される. --]] print(key, value) end

比較演算子

比較演算子では偽の時にfalse、真の時にtrueを返します。

num1 == num2 -- num1はnum2と等しい. num1 ~= num2 -- num1はnum2と等しくない. num1 < num2 -- num1はnum2より小さい. num1 > num2 -- num1はnum2より大きい. num1 <= num2 -- num1はnum2以下. num1 >= num2 -- num1はnum2以上.

7. 関数

Luaでは関数を作るにはfunction(...) ~ endを使います。また、関数内に関数を作ることができます。

function sum(num1, num2) local total = num1 + num2 return total end sum = function(num1, num2) local total = num1 + num2 return total end

8. ファイル入出力

Lua独自のファイル入出力として、io.input, io.output関数があります。初期状態で標準入力、標準出力に割り当てられています。

io.input("input.txt") -- 入力をinput.txtから読み込む. io.output("output.txt") -- 出力をoutput.txtに書き出す. while line = io.read() do print(line) -- io.write(line .."\n")と同じ. end io.input() -- 入力を標準入力にする. io.output() -- 出力を標準出力にする.

C言語のようなファイル入出力にはio.open関数を使います。

--[[ r: 読み込み, w:書き込み, a:追記, r+, w+, a+ fh: ファイルハンドル, msg: エラーメッセージ. --]] fh, msg = io.open("input.txt", "r") if fh then data = fh:read("*a") -- ファイル全体を読み込む. print(data) else print(msg) end

9. C言語とLuaの連携

LuaではC言語と連携を取るためのAPIが充実しているので、C言語からLuaを利用することも、LuaからC言語を利用することも簡単です。基本的な流れとして、スタックに積んだデータをやりとりすることになります。

以下にサンプルコードを示しておきます。LuaからC言語を利用する場合も、C言語からLuaを利用する場合も、C言語のソースコードをコンパイルしたファイルから実行されます。

ヘッダファイルやライブラリのパスを通した上で、Visual Studioでは、

cl func.c lua51.lib

gccでは、

gcc func.c -llua -lm

でコンパイルできます。

func.lua

print(func1(1.5)) -- C言語で作成した関数func1を呼び出す. data = { test = "lua" } print(func2(data)) -- C言語で作成した関数func2を呼び出す.

func.c

#include #include "lua.h" #include "lualib.h" #include "lauxlib.h" /* 引数で渡された数値を半分と倍にして返す */ int func1(lua_State *L) { /* 最初のスタックから数値を読み出す */ double d = lua_tonumber(L, 1); lua_pushnumber(L, d / 2.0); /* 半分にしてスタックに載せる */ lua_pushnumber(L, d * 2.0); /* 倍にしてスタックに載せる */ return 2; /* 戻り値のスタック数 */ } /* 引数で渡されたテーブル内のtestフィールドの文字列を返す */ int func2(lua_State *L) { /* スタックをクリアする */ lua_settop(L, 1); /* テーブル内のフィールド"test"をスタックの最初に置く */ lua_getfield(L, 1, "test"); /* スタックの2番目(つまりフィールド"test"のデータ)の */ /* 文字列を読み出してスタックに置く*/ lua_pushstring(L, lua_tostring(L, 2)); return 1; /* 戻り値のスタック数 */ } int main() { /* Luaを使えるようにする */ lua_State *L = luaL_newstate(); /* Luaの標準関数を利用可能にする */ luaL_openlibs(L); /* C言語で作成した関数をLua上で呼び出せるようにする */ lua_register(L, "func1", func1); lua_register(L, "func2", func2); /* Luaソースファイル"func.lua"を読み出して実行する */ /* func.lua内でfunc1, func2を利用している */ if (luaL_loadfile(L, "func.lua") || lua_pcall(L, 0, 0, 0)) { fprintf(stderr, "error\n"); return -1; } /* luaL_dostringを利用してC言語からLuaを使用する */ luaL_dostring(L, "print(3.5)"); /* スタックを利用してC言語からLuaを利用する */ lua_getglobal(L, "print"); /* print関数をスタックに置く */ lua_pushstring(L, "string"); /* 文字列"string"をスタックに置く*/ lua_pcall(L, 1, 0, 0); /* 引数1個、戻り値なし、エラー関数なしで実行 */ lua_close(L); return 0; }

出力結果:

0.75 3 lua 3.5 string

上記の方法だと、LuaからC言語の関数を呼び出す場合でもC言語からLuaのコードを呼ばなくてはならず、コンパイルが必要になります。そこで、以下にC言語による関数をWindowsのDLLファイルおよびUnixの共有ライブラリファイルのモジュールとして呼び出す方法を書いておきます。関数の内容は上記のコードと同じで、func1とfunc2を定義しています。

Visual Studioでのコンパイル方法は以下の通りです。

cl cmodule.c lua51.lib /LD

gccでのコンパイル方法は以下のようになります。

gcc cmodule.c -llua -shared -o cmodule.so

実行は、通常のLuaスクリプトと同じです。

lua func_mod.lua

func_mod.lua

require("cmodule") -- cmodule.dll/cmodule.soを読み込む. print(cmodule.func1(1.5)) -- C言語で作成した関数func1を呼び出す. data = { test = "lua" } print(cmodule.func2(data)) -- C言語で作成した関数func2を呼び出す.

cmodule.c

#include <stdio.h> #include "lua.h" #include "lauxlib.h" /* 引数で渡された数値を半分と倍にして返す */ static int func1(lua_State *L) { /* 最初のスタックから数値を読み出す */ double d = lua_tonumber(L, 1); lua_pushnumber(L, d / 2.0); /* 半分にしてスタックに載せる */ lua_pushnumber(L, d * 2.0); /* 倍にしてスタックに載せる */ return 2; /* 戻り値のスタック数 */ } /* 引数で渡されたテーブル内のtestフィールドの文字列を返す */ static int func2(lua_State *L) { /* スタックをクリアする */ lua_settop(L, 1); /* テーブル内のフィールド"test"をスタックの最初に置く */ lua_getfield(L, 1, "test"); /* スタックの2番目(つまりフィールド"test"のデータ)の */ /* 文字列を読み出してスタックに置く*/ lua_pushstring(L, lua_tostring(L, 2)); return 1; /* 戻り値のスタック数 */ } /* モジュールに登録するために関数のテーブルを定義 */ static luaL_Reg cmodule[] = { { "func1", func1 }, { "func2", func2 }, { NULL, NULL } /* 最後は NULL, NULL とする */ }; /* DLL/共有ライブラリにエクスポートする関数 luaopen_(モジュール名) */ /* Unixでは__declspec(dllexport)は不要 */ __declspec(dllexport) int luaopen_cmodule(lua_State *L) { luaL_register(L, "cmodule", cmodule); /* cmoduleモジュール */ return 1; }

出力結果:

0.75 3 lua

知っておいた方がよい文法

真偽値

falseとnil以外はすべて真になります。よって、0や""や空のテーブルなどもすべて真です。

論理演算子

a = 10 b = 30 c = a < 20 or b < 20 -- true d = a < 20 and b < 20 -- false a = 10 or 20 -- a = 10 a = 10 and 20 -- a = 20 a = nil or 10 -- a = 10 a = nil and 10 -- a = nil

初期化されていない変数はすべてnilが入っていることを利用して、orを使ってデフォルト値を設定することができます。

a = a or 10 -- aに何も代入されていなければ10が代入される.

外部Luaファイルの読み込み

外部Luaファイルの実行やテーブルなどのデータを読み込む目的で利用できます。

dofile("file.lua")

文字列をプログラムとして実行

Perlなどのeval機能として、loadstring関数があります。

f = loadstring("i = i + 1") i = 0 f() f() f() print(i) -- 3と表示される.

データの型を文字列で返す

type(1) -- "number" type("abc") -- "string" type({ 1, 2, 3 }) -- "table" type(true) -- "boolean" type(print) -- "function"

数値と文字列の変換

tonumber("123.5") -- 123.5 tostring(123.5) -- "123.5"

参考サイト


他の基礎文法最速マスター


続きを読む...