RubotyでSKIコンビネータ計算する
RubotyはRubyで書かれたチャットボットフレームワークです。
RubotyはRubyでプラグインを実装して拡張できます。つまりRubyでできることはRubotyでできます。便利ですね。
ということでRubotyでSKIコンビネータ計算をしてみました。 しかし、Rubyでプラグインを書いたら簡単に実装できてしまってつまらないですね。1 そのため今回は、自分ではプラグインを書かずにSKIコンビネータ計算を実装しました。
SKIコンビネータ計算とは
私も今回初めてSKIコンビネータ計算について調べました。ちゃんとした説明ができる気がしないので、Wikipediaなりなんなりを読むと良いと思います。
使用するプラグイン
今回はプラグインの実装はしませんが、既存のプラグインを2つ使います。ruboty-replaceとruboty-echoです。
ruboty-replace
ruboty-replaceは、受け取った発言を正規表現で置換して、それを発言として解釈するプラグインです。 次のように使えます。
# https://github.com/makimoto/ruboty-rainfall_jp を使う例です # replaceする前 # コマンドが少し長い > ruboty tell me rainfall at Kyoto Rainfall forecast: Kyoto (135.75141900,35.01042850) 07-18 22:15 0.0 mm/h 07-18 22:25 0.0 mm/h 07-18 22:35 0.0 mm/h 07-18 22:45 0.0 mm/h 07-18 22:55 0.0 mm/h 07-18 23:05 0.0 mm/h 07-18 23:15 0.0 mm/h # replaceをする > ruboty replace tenki with tell me rainfall at Kyoto Registered # replace後 # replace前と同じコマンドが実行される > ruboty tenki
なお、今回の例だとruboty image tenki
のようなコマンドも同様に置換されてしまうので、使う際には少し注意が必要です。
ruboty-echo
こちらはとても単純なプラグインで、Rubotyに発言をさせられます。
次の例のように、ruboty echo
に続く文字列をrubotyが喋ります。
> ruboty echo hello hello
これだけだと単純ですが、RubotyはRuboty自身の発言もトリガーとすることができます。2 つまり、次のようなことができます。
> ruboty echo ruboty echo hello ruboty echo hello hello
この例では、Rubotyに「ruboty echo hello」と言えと命令し、そしてRubotyが自分自身に「hello」と言えと命令し、その結果「hello」が出力されています。
ruboty-replace と ruboty-echo の組み合わせ
そして、この2つのプラグインを組み合わせることでループを作れます。たとえば無限ループを作るには次のようにすると良いでしょう。
> ruboty replace ruboty loop with ruboty echo ruboty loop Registered > ruboty loop ruboty loop ruboty loop ruboty loop
とても便利そうな気がしてきましたね。
ruboty-replaceは与えられた文字列をgsub!
するので、gsub!
とwhile
だけでRubyでSKIコンビネータ計算を実装すれば、それをそのままRubotyに移植できそうです。
SKIコンビネータ計算の実装
さて、今回使う道具は揃いました。 この2つのプラグインを使えばSKIコンビネータ計算を実装できます。
今回は次のような方針で実装しました。
ruboty ski <式>
のようなコマンドを、各法則に従って書き換えていく正規表現をruboty-replaceで登録する- 各法則を1つずつ適用した後、まだ計算を適用できれば
ruboty echo ruboty ski <式>
する - これ以上計算できなければ
ruboty echo <式>
をして計算結果を表示して終了する
そして、実際にruboty-replaceに登録した命令は次になります。
ruboty replace ^ruboty ski (?<simple>(?:[()]|[^ski])*)s(?<expr1>[^()]|\(\g<expr1>+\))(?<expr2>[^()]|\(\g<expr2>+\))(?<expr3>[^()]|\(\g<expr3>+\))(?<rest>.*) with ruboty ski \k<simple>(\k<expr1>\k<expr3>)(\k<expr2>\k<expr3>)\k<rest> ruboty replace ^ruboty ski (?<simple>(?:[()]|[^ski])*)k(?<expr1>[^()]|\(\g<expr1>+\))(?<expr2>[^()]|\(\g<expr2>+\))(?<rest>.*)$ with ruboty ski \k<simple>\k<expr1>\k<rest> ruboty replace ^ruboty ski (?<simple>(?:[()]|[^ski])*)\((?<content>(?<expr1>[^()]|\(\g<expr1>+\))*)\)(?<rest>.*) with ruboty ski \k<simple>\k<content>\k<rest> ruboty replace ^ruboty ski (?<simple>(?:[()]|[^ski])*)i(?<rest>.+) with ruboty ski \k<simple>\k<rest> ruboty replace ^ruboty ski (?<simple>(?:[()]|[^ski])*)$ with ruboty echo \k<simple> ruboty replace ^ruboty ski (?<content>.*[ski].*)$ with ruboty echo ruboty ski \k<content>
これを登録すればお使いのチャットサービスでSKIコンビネータ計算ができて便利…!
なのですが、「まだ計算を適用できれば〜」の条件をとても雑に書いているため、注意が必要です。3
具体的にはruboty ski s
などと発言するだけで容易に無限ループします。
お試しの際はどうか自己責任でお願いいたします。
実装の説明
実はこの正規表現は手書きしたのではなく、次のRubyスクリプトを書いて生成しました。
expr_gen = -> (n) { "(?<expr#{n}>[^()]|\\(\\g<expr#{n}>+\\))" } simple = '(?<simple>(?:[()]|[^ski])*)' S = /^#{simple}s#{expr_gen.(1)}#{expr_gen.(2)}#{expr_gen.(3)}(?<rest>.*)/ K = /^#{simple}k#{expr_gen.(1)}#{expr_gen.(2)}(?<rest>.*)$/ I = /^#{simple}i(?<rest>.+)/ P = /^#{simple}\((?<content>#{expr_gen.(1)}*)\)(?<rest>.*)/
また、Slackに登録する前に次のコードで動作を確認しながら実装していました。 https://gist.github.com/pocke/48417a4b119c84520f5176fe15f19667
expr_gen
まず、expr_gen
は"式"にマッチする正規表現として使う文字列を生成する関数です(なんとなく「式」と言っているけど、これをなんて呼ぶべきなのかはよくわからない)。
式は「カッコではない文字1文字」もしくは「カッコの中に1つ以上の式が連続している文字列」を指します。
つまり、x
や(xy)
や((xy))
や(x(y))
は式です。
一方xx
や()
や(()
は式ではありません。
なお文字列を生成するようにしているのは、1つの正規表現内で複数回式を使う時に、キャプチャのための名前が被らないようにするためです。
expr_gen.(1)
のように呼び出すと、expr1
という名前でキャプチャを作るようになっています。
ところで、「おや?」と思った方がいるかも知れません。 この正規表現は明らかにカッコの対応を取る必要がありますが、普通正規表現はカッコの対応を検出できません。 ですが、Rubyの正規表現では「部分式呼び出し」という機能を使うことでカッコの対応を検出できます。 詳しくは https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html#subexp を参考にしてください。
simple
simpleは、「これ以上変換できない文字列」にマッチする正規表現として使う文字列を生成する関数です。 そのような文字列は単に読み飛ばして、後続の文字列を評価するようにしています。
S, K, I
そのままSKIコンビネータ計算の各関数にマッチする正規表現です。
P
これは、余分なカッコを外すための正規表現です。
SKIコンビネータ計算の実行
これを実際にSlackに登録して実行してみると、次のようになります。 今回はWikipediaに載っている「式の逆転」の例を試してみました。
計算が動いていて面白いですね。
みなさんもRubotyやSKIコンビネータ計算で遊んでみてはいかがでしょうか。