はじめに
Pharo SmalltalkからTarantool経由でLuaを実行してみようという話です。
Tarantoolをインストール
Tarantoolって何よ? ということですが、DBMSの機能を備えたLuaの実行環境です。ロシア製ということもあって日本ではまだマイナーですが、向こうでは大手のネット企業であるMail.Ru Groupが使ったりもしているようです。
https://tarantool.org/try.html
でいきなりWebブラウザから試せます。
3+4
と打ってenterすると
7
が返ってきますね。
正直ここで試すのは苦痛なのでとっとと本物をインストールします。
macOSであれば
brew install tarantool --HEAD
とします。
その他OS用のインストール方法はこちらです。
https://tarantool.org/download.html
Tarantoolの起動
では動かしてみます。
tarantool
とターミナルで打ってみるとプロンプトが出てきます。
tarantool> 3 + 4
7
が返ってきました。
tutorial()
と打つとインタラクティブなチュートリアルが始まったりしますが、今回は割愛します。
DBMS部分を動かしてみましょう。
box.cfg{listen = 3301}
3301ポートで接続を受け付けるようになりました。
次に外部接続用のユーザを作成します。'taran'というユーザIDで、パスワードは'talk'にします。
box.schema.user.create('taran', {password = 'talk'})
さらに権限も付与します。データの読み取りだけでなく、スキーマやインデックスの作成、evalもできるようにしておきます。
box.schema.user.grant('taran', 'read,write,execute', 'universe')
これでクライアントが接続できる環境が整いました。
Tarantalkを入れる
TarantalkとはPharo版のTarantoolクライアントです。これによりPharo SmalltalkからTarantoolにつないで、Luaのプログラムを実行したり、DBMSの機能を使ったりすることが可能になります。
TarantoolのクライアントはConnectorと呼ばれています。Smalltalk以外にも、JavaやPython、ErlangやC#などいろいろな言語用のConnnectorが提供されています。
https://tarantool.org/doc/book/connectors/index.html
TarantalkはPharoのCatalog Browserからインストールできます。"Tools"->"Catalog Browser"で開き、検索窓のところに"taran"と打てば見つかるでしょう。
右クリックで"Install stable version"を選ぶと入ります。
もしくはPlaygroundを開き、以下を"Do it"でも良いです。
Metacello new
smalltalkhubUser: 'Pharo' project: 'MetaRepoForPharo50';
configuration: 'Tarantalk';
version: #stable;
load.
接続の確認
Tarantoolには認証機能があるので、接続の際には、ホスト、ポート番号の他、ユーザIDとパスワードをURIの形で指定します。
tarantalk := TrTarantalk connect: 'taran:talk@localhost:3301'.
"Print it"してみると接続状態を確認できます。
接続を切るにはrelease
を送ります。
tarantalk release.
TrTarantalk releaseAll
で、複数のクライアントをまとめてrelease
することもできます。色々試した結果、エラーなどでTarantoolへの接続状態がおかしくなった時に使うと便利です。
Luaプログラムの実行
DBMSとしての利用は次回に回すとして、ここではLuaのプログラムをPharoから実行してみることにします。evalWithReturn:
を使います。
tarantalk := TrTarantalk connect: 'taran:talk@localhost:3301'.
(tarantalk evalWithReturn: '3+4') value.
#(7)
が返ってきました。evalWithReturn:
は必ずArrayの形で戻り値が返ります。これはLuaの関数が多値を返すことができるためです。
value
と送っているのは、TarantoolのAPIが非同期になっているからです。value
は非同期APIを同期的に使うための便利メッセージです。これにより値が返るのをブロックして待つようになります。
非同期としてそのまま使うにはコールバックをifDone:ifFailed
で指定します。
(tarantalk evalWithReturn: '3+4')
ifDone: [:ret | ret inspect] ifFailed: [:err | err inspect].
"Do it"するとevalの終了時に別のスレッドでインスペクタが上がります。非同期APIはブロックが最小限になるのでスループットが上がるという利点があります。が、記述が面倒なので以後は主にvalue
を使います。
今度は乱数を得てみましょう。
(tarantalk evalWithReturn: 'math.random()') value.
もうちょっとコラボ感を出したいので、Luaからの値をもとに、Smalltalk側で現在の時間を生成してみたいと思います。別にDateAndTime now
で良いのですがあえてやります。
(tarantalk evalWithReturn: 'os.time()') valueThen: [ :val |
DateAndTime fromUnixTime: val first
].
valueThen:
はvalue
のバリエーションで、Luaからの値を加工したい時に使います。os.time()
の結果が数値の配列として帰るのでfirst
で取り出し、DataAntTime class>>fromUnixTime:
に渡しているというわけです。
"Inspect it"すると現在時間となっていることを確認できます。Unix epochからの秒数なので、ナノ秒の部分(nanos
)は入っていません。
ちなみにDateAndTime now
だとこうなります。
Lua側に値を渡す
今度はSmalltalk側からLuaのプログラムに値を渡してみる例です。
値を渡すためにはevalWithReturn:arguments:
を使います。Lua側も(...)
で任意の引数を受け取れるように書いておく必要があります。
Smalltalkにも正規表現ライブラリはありますが、あえてLuaの文字列パターンマッチを利用してみます。
(tarantalk evalWithReturn: 'string.match(...)' arguments: {'こんにちはSmalltalkの世界へ'. 'S%w+'}) value
'S'
で始まるalphanumericな部分が取り出されます。'Smalltalk'
ですね。
requireしてみる
TarantoolはLuaの標準ライブラリ以外にも、便利ライブラリを色々と搭載しているので、requireして使ってみたいと思います。
搭載ライブラリについてはこちらに情報があります。
https://tarantool.org/doc/reference/reference_lua/index.html
digestのmoduleを使い、SHA-512のハッシュ値を求めてみたいと思います。Pharoには標準でSHA-256はあるのですが、SHA-512は入っていないのです。ようやくLuaを使う意味が出てきました。
(tarantalk evalWithReturn: '(require("digest").sha512_hex(...))' arguments: {'こんにちはSmalltalkの世界へ'}) valueThen: [ :val |
val first
].
それらしい結果が返ってきます。
速さは?
興味本位で速さを測ってみました。1000回ほど繰り返してみます。
[1000 timesRepeat: [
(tarantalk evalWithReturn: '(require("digest").sha512_hex(...))' arguments: {'こんにちはSmalltalkの世界へ'}) valueThen: [ :val |
val first
].
]] timeToRun.
2013年のMacBook Air (1.7 GHz Intel Core i7)で0:00:00:00.334
となりました。1秒間で3000送信くらいはこなせそうです。FFIには遠く及びませんがお気軽には使えるでしょう。
まとめ
Tarantalkというライブラリを使ってSmalltalkからLuaを実行してみようという話でした。豊富なLuaのエコシステムをPharo Smalltalkから利用できるというメリットがあります。
DBMSとしても面白い特徴がありますので、それは次回以降に紹介します。