SmalltalkからLuaをevalする

# はじめに

Pharo SmalltalkからTarantool経由でLuaを実行してみようという話です。

# Tarantoolをインストール

[Tarantool](https://tarantool.org)って何よ? ということですが、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`が返ってきました。

![tarantool-console.png](https://qiita-image-store.s3.amazonaws.com/0/61787/30d52263-c21a-c40a-048b-f23eade822fa.png)


`tutorial()`と打つとインタラクティブなチュートリアルが始まったりしますが、今回は割愛します。

DBMS部分を動かしてみましょう。

```lua
box.cfg{listen = 3301}
```

3301ポートで接続を受け付けるようになりました。

![tarantool-listen.png](https://qiita-image-store.s3.amazonaws.com/0/61787/987f8f49-cffb-ce19-7c09-0915c237e268.png)

次に外部接続用のユーザを作成します。'taran'というユーザIDで、パスワードは'talk'にします。

```lua
box.schema.user.create('taran', {password = 'talk'})
```

さらに権限も付与します。データの読み取りだけでなく、スキーマやインデックスの作成、evalもできるようにしておきます。

```lua
box.schema.user.grant('taran', 'read,write,execute', 'universe')
```

これでクライアントが接続できる環境が整いました。

# Tarantalkを入れる

[Tarantalk](http://smalltalkhub.com/#!/~MasashiUmezawa/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"と打てば見つかるでしょう。

![catalog-browser.png](https://qiita-image-store.s3.amazonaws.com/0/61787/f3f17bff-acba-50bb-e352-e8ae09c8b86f.png)

右クリックで"Install stable version"を選ぶと入ります。

![install-stable-version.png](https://qiita-image-store.s3.amazonaws.com/0/61787/d5bea8b9-3438-a9a7-5cec-d54169359169.png)

もしくはPlaygroundを開き、以下を"Do it"でも良いです。

```smalltalk
Metacello new
	smalltalkhubUser: 'Pharo' project: 'MetaRepoForPharo50';
	configuration: 'Tarantalk';
	version: #stable;
	load.
```

# 接続の確認
Tarantoolには認証機能があるので、接続の際には、ホスト、ポート番号の他、ユーザIDとパスワードをURIの形で指定します。

```smalltalk
tarantalk := TrTarantalk connect: 'taran:talk@localhost:3301'.
```

"Print it"してみると接続状態を確認できます。

![tarantool-connected.png](https://qiita-image-store.s3.amazonaws.com/0/61787/4e438bc0-1077-cc3f-fa66-22fcf35ee37d.png)

接続を切るには`release`を送ります。

```smalltalk
tarantalk release.
```

![tarantool-released.png](https://qiita-image-store.s3.amazonaws.com/0/61787/c9c5feaf-e432-0f2b-bf1a-eb96f4fa3589.png)


`TrTarantalk releaseAll`で、複数のクライアントをまとめて`release`することもできます。色々試した結果、エラーなどでTarantoolへの接続状態がおかしくなった時に使うと便利です。

# Luaプログラムの実行

DBMSとしての利用は次回に回すとして、ここではLuaのプログラムをPharoから実行してみることにします。```evalWithReturn:```を使います。

```smalltalk
tarantalk := TrTarantalk connect: 'taran:talk@localhost:3301'.
(tarantalk evalWithReturn: '3+4') value.
```

`#(7)`が返ってきました。`evalWithReturn:`は必ずArrayの形で戻り値が返ります。これはLuaの関数が多値を返すことができるためです。

![return7.png](https://qiita-image-store.s3.amazonaws.com/0/61787/c172d044-246c-3b76-eb1d-855ac99d58dc.png)

`value`と送っているのは、TarantoolのAPIが非同期になっているからです。`value`は非同期APIを同期的に使うための便利メッセージです。これにより値が返るのをブロックして待つようになります。

非同期としてそのまま使うにはコールバックを`ifDone:ifFailed`で指定します。

```smalltalk
(tarantalk evalWithReturn: '3+4')
	ifDone: [:ret | ret inspect] ifFailed: [:err | err inspect].
```

"Do it"するとevalの終了時に別のスレッドでインスペクタが上がります。非同期APIはブロックが最小限になるのでスループットが上がるという利点があります。が、記述が面倒なので以後は主に`value`を使います。

![inspect7.png](https://qiita-image-store.s3.amazonaws.com/0/61787/c6b5dbf6-ecc1-cb58-cc4d-78a4671dde9b.png)

今度は乱数を得てみましょう。

```smalltalk
(tarantalk evalWithReturn: 'math.random()') value.
```

`#(0.698852465637164)`と返ってきました。
![rand.png](https://qiita-image-store.s3.amazonaws.com/0/61787/eb914e79-49f9-d652-bd24-aaaaf0445b31.png)

もうちょっとコラボ感を出したいので、Luaからの値をもとに、Smalltalk側で現在の時間を生成してみたいと思います。別に`DateAndTime now`で良いのですがあえてやります。

```smalltalk
(tarantalk evalWithReturn: 'os.time()') valueThen: [ :val |
	DateAndTime fromUnixTime: val first
].
```

`valueThen:`は`value`のバリエーションで、Luaからの値を加工したい時に使います。`os.time()`の結果が数値の配列として帰るので`first`で取り出し、`DataAntTime class>>fromUnixTime:`に渡しているというわけです。

"Inspect it"すると現在時間となっていることを確認できます。Unix epochからの秒数なので、ナノ秒の部分(`nanos`)は入っていません。

![inspect-lua-dateandtime.png](https://qiita-image-store.s3.amazonaws.com/0/61787/56b01b13-ff52-264e-58b5-075730f97dfc.png)


ちなみに`DateAndTime now`だとこうなります。

![inspect-st-dateandtime.png](https://qiita-image-store.s3.amazonaws.com/0/61787/4e2e81d0-e27e-360d-850f-b0727a7acaff.png)

# Lua側に値を渡す

今度はSmalltalk側からLuaのプログラムに値を渡してみる例です。
値を渡すためには`evalWithReturn:arguments:`を使います。Lua側も`(...)`で任意の引数を受け取れるように書いておく必要があります。

Smalltalkにも正規表現ライブラリはありますが、あえてLuaの文字列パターンマッチを利用してみます。

```smalltalk
(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を使う意味が出てきました。:slight_smile:

```smalltalk
(tarantalk evalWithReturn: '(require("digest").sha512_hex(...))' arguments: {'こんにちはSmalltalkの世界へ'}) valueThen: [ :val |
	val first
].
```

それらしい結果が返ってきます。

![sha512.png](https://qiita-image-store.s3.amazonaws.com/0/61787/12766f91-67be-bbd4-25e8-27f93e176595.png)

# 速さは?

興味本位で速さを測ってみました。1000回ほど繰り返してみます。

```smalltalk
[1000 timesRepeat: [  
(tarantalk evalWithReturn: '(require("digest").sha512_hex(...))' arguments: {'こんにちはSmalltalkの世界へ'}) valueThen: [ :val |
	val first
].
]] timeToRun.
```

![perf.png](https://qiita-image-store.s3.amazonaws.com/0/61787/c0bfb136-3182-f8f1-8e43-b24969f82644.png)


2013年のMacBook Air (1.7 GHz Intel Core i7)で`0:00:00:00.334`となりました。1秒間で3000送信くらいはこなせそうです。FFIには遠く及びませんがお気軽には使えるでしょう。

# まとめ
Tarantalkというライブラリを使ってSmalltalkからLuaを実行してみようという話でした。豊富なLuaのエコシステムをPharo Smalltalkから利用できるというメリットがあります。
DBMSとしても面白い特徴がありますので、それは次回以降に紹介します。