Mobbの正規表現解釈と、MatchDataの行方


このエントリは、 Mobb/Repp Advent Calendar の十四日目です

Mobbの扱う正規表現

Mobbでは、 on/receive メソッドの引数として正規表現を渡すことが出来ます。

この部分は現在、次のようなコードで解釈がされています。

Reppからの入力が正規表現に一致した場合、 Mobb::Matcher::Matched オブジェクトが作成され、その中のキャプチャ結果を on/receive のブロックに対して引数として渡しています。

このように、Mobbは正規表現のマッチ結果を受け取れる能力はあるのですが、あるひユーザーのひとりに「名前付きキャプチャを使いたいから、RegexpのMatchDataをそのまま触らせって欲しい」という要望を伝えられました。

そのような用途もあることは理解できるので、どうにかMatchDataをユーザーが触れるように提供してみようと思い、次のような構文を考えています。

このBotに対して、 “hey hey hey” と呼びかけると、 “hey” という文字列が返ってくるようにしたいとおもっています。つまり、 on/receive の引数に正規表現をとった場合、matchedというアクセサがブロックの中で利用でき、呼び出すとRegexp#matchの戻り値が得られるような構文を考えています。

これらは、Sinatraでいうところの request/response/params といったアクセサと同じ扱いになりますが、SinatraがRackからの呼び出しの直後にこれらの変数を初期化するのに対し、Mobbでは初期化のタイミングがすこし遅くなることが変更点となるでしょう。

機能追加のリクエストお待ちしてます

Mobbの次のバージョンは、年内にリリース予定です。よろしくおねがいします。


カテゴリー: 未分類 | コメント / トラックバック: 0個

BotはBotと会話するべきかどうか?


このエントリは、  Mobb/Repp Advent Calendar の十三日目です

BotとBotの関係性

突然ですが、このBotは無限ループを引き起こします。

Yoという発言を受け取り、Yoと自分も発言すると、自分の発言を拾って更にYoが無限にYoするからです。Mobbでは、これを防ぐために ignore_bot というコンディションフィルタを用意しています。

ignore_botコンディションを利用することによって、このonのブロックはBotに反応しなくなります。したがって、自分自身のYoの発言に自分で反応することもなくなります。(もっとも、これはReppハンドラが正しく人間とBotのアカウントを判断して情報を送ってくれるときに限るので、例えばBotと人間の区別がつかないサービスの場合はもちろん機能しません)

チュートリアルを紹介したエントリでも言及しましたが、この ignore_bot というフィルタは、今後デフォルトで機能するように変わる予定です。すなわち、次のバージョンのMobbでは、最初のコードと次のコードが全く等価になるということです。そのかわりに、次のバージョンからは react_to_bot と include_myself コンディションが用意されます。このコンディションをtrueに設定すると、BotはBotのメッセージに反応するようになります。

なぜこのような変更が入るかというと、BotとBotの関係性と安全性の問題があるからです。

Botフレームワークに限らず多くのライブラリは、記述の簡単さと、自由さと、安全性を天秤にかけることになります。Mobbは、その中でも簡単さと自由さに重点を置いていて、安全性に関しては優先度を下げていました(ここでいう安全性とは、ライブラリのセキュリティではなく、問題を起こしそうな記述を未然に防いでくれるかどうかということです)。そのため、Botが発したメッセージに関しても、ユーザーは特に何も考えることなくアクセスが可能で、Botに反応したくない場合はそれを明示的にユーザーが拒否すればいい、デフォルトでBotが可能な限り多くの情報に触れられるようにしたほうが良いという考え方で始まりました。しかしその一方で、この方針は無限ループを容易に引き起こしたり、無意味な発言を繰り返したりという行儀の良くない行動に関しても、記述するユーザーに責任を求めることになりました。

Mobbの安全性への姿勢は、リリース後に様々な人に触れてもらう中で、必要以上に軽視されているということが指摘されました。そのため、Mobbは次のバージョンから、いたずらに無限ループを起こしたり、他のBotにちょっかいを出す行儀の悪い行為をデフォルトで禁止し、そうしたい場合はユーザーに明示的に指示を求めるように変更することになりました。

次のバージョンからは、Mobbは自分を含む全てのBotの発言をデフォルトで無視します。これは、Botの安全性を高めるためです。そして、Botの発言を意図的に取り込みたい場合は、次のような記述が必要になります。

react_to_botコンディションは、自身を含めない他のすべてのBotの発言に対して反応するようになるコンディションです。そして、 include_myself コンディションは、自分自身の発言に対しても反応するようにするコンディションです。

ほぼすべてのケースにおいて、Botの作成者は react_to_bot コンディションを利用することでやりたいことがすべて実現すると思います。 include_myself は、昨日のエントリで紹介した chain/trigger 構文によって、自分自身への反応をする必要が無いからです。しかし、どうしてもすべてのメッセージに目を通したいというユーザー(例えば、自身を含む全ての発言を記録して、自身は何もしないBotを作りたいなど)のために、自分以外へのBotの反応、自分を含めたすべてのBotへの反応、という二段階の安全策を用意します。

記述性、自由度、安全性でバランスの取れた設計に

次のバージョンで追加されるコンディションで、Mobbは記述性をわずかに失い、安全性を大きく手に入れます。これは、必要なトレードオフです。しかし、Mobbの基本的な方針として、記述性と自由度を重視していく姿勢は変わりません。

次のバージョンのMobbは、年内にリリース予定です。よろしくおねがいします。


カテゴリー: 未分類 | コメント / トラックバック: 0個

Mobbのメソッド呼び出しをチェーンする、 chain/trigger シンタックス


このエントリは、 Mobb/Repp Advent Calendar の十二日目です

Mobbにできないこと

これは、Mobb/Reppの未来の実装の話です。

Mobb(というよりも、Repp)の最大の特徴に、「発言を受け取り、返答を戻り値で返す」というシンプルな記述があります。これはとても強力でシンプルなルールですが、その一方でいくつか困ったことも起こります。

たとえば、Botにあるとても時間のかかる作業がさせたいとします。何も考えずに実装すると、その作業の起動トリガーを発言し、Mobbがそれを受け取り作業をし、完了したという文字列を返します。これは一見何もおかしなことは無いように見えますが、一つ大きな問題を抱えています。それは、時間のかかる作業の間、Botは無言で作業をこなしていて、その作業を起動させた人は、Botがちゃんと仕事をしているのか、それとも無言で死んだのかを区別する方法がありません。

次に、少し人間に優しい実装をしてみましょう。無言で死ぬのではなく、何かしらのエラーが発生した場合は、それを戻り値としてサービスに返せば、少なくとも何かエラーが発生したことはわかります。エラーメッセージの返し方によっては、何が原因で失敗したのかという細かい情報も得られるでしょう。これで概ね困ることは無いように思いますが、本当にそうでしょうか? この実装では、Botが作業を開始する指示を正しく受け取ってくれたのかどうかがわからないという問題が残っていませんか?

最後に、完全に人に優しい実装を考えてみましょう。作業の起動トリガーとなる発言を受けると、Botは即座に「作業を開始します」と返答します。その後、時間のかかる作業をやりながら、適宜「いまXXまで作業が終わりました」と教えてくれれば、ものすごく優しくないでしょうか? 少なくとも、コマンドを間違えてBotが起動してくれなかった場合などにはすぐ異常を検知できますし、何よりBotに愛着がわきます。

ところが、現状のMobb/Reppの構成では、このような適宜返事を返すBotと言うものを作ることが出来ません。なぜなら、最初に書いたとおり、Mobb/Reppはメソッドの戻り値がサービスの発言となり、途中経過で何かしらのアクションをすることが出来ないからです。そして、Reppはインターフェイスというその思想から、Mobb側にサービスに対してアクションをする方法は完全に秘匿されており、何もすることはできません。これは、よくあるBotフレームワークが、処理の途中で適宜サービスへのアクセスができることと大きな違いです。

しかし、Mobb/Reppは秒でクソボットを作れるフレームワークであると同時に、快適なBotを人々に提供することも軽視しているわけではありません。それでは、この問題を解決するにはどうすればいいでしょうか?

chain/trigger

回答の一つは、chain/trrigerという新しいDLSです。

これから記述するコードは、全てまだアイディア段階のもので、実際にMobb/Reppに実装されているものではありません。そのため、実際に実装されるときは形が変わる可能性もあることを了承してください。

通常通り、onでサービスからの発言を受け取り、文字列を返すところまでは変わりません。しかし、onのブロックの中で、chainという一つの引数を取る今までに無いメソッドを呼び出しています。

そして、triggerという新しいDSLが追加されています。このtriggerは、一つの引数を受け取り、ブロックを取ります。これは、onやcronと同じです。

おそらく直感的にわかると思いますが、onの中で呼び出されたchainの引数であるシンボルと一致するtriggerが、onの終了後に呼び出されます。フロー的には次のようになります。

  1. onブロックの処理が開始される
  2. chainで、:slow_taskという連鎖を追加する
  3. onブロックが終了し、Reppが戻り値(ここでは「時間のかかる作業をします」という文字列)をサービスに投稿します
  4. Reppが、chainで登録された情報をもとに、再度Mobb側に情報を渡します。このとき渡される情報は、最初のonブロックに入ったときと等しく扱える情報が渡されます
  5. Mobbは、:slow_task というtriggerが起動されたことを検知し、triggerのブロックを実行します。ブロックの中では、very_slow_taskというヘルパーが呼び出されます
  6. triggerブロックが終了し、Reppがの戻り値(ここでは「時間のかかる作業が終わりました」という文字列)をサービスに投稿します

これまでのMobb/Reppであれば、1から3までの手順のみが実行可能でした。しかし、次のバージョンのMobb/Reppからは、処理をchain/triggerで連鎖させることで、ある作業の途中経過にサービスにアクセスするという処理を表すことができます。

このように、triggerのなかからさらにchainすることも可能にする予定です。

そしてこのように、chainを使って複数のタスクを並列で連鎖させることもできるようにしようと思います(これはExperimentalで、本当に可能かどうかはわかりません。というのも実現は可能ですが、Repp側で正しく制御することを強制できるかどうかはわからないためです)。

Mobb 0.5.0

この機能は、次のリリースのMobbで実装される予定です。次のバージョンは、年内に出ればいいなくらいの気持ちで考えています。

本当は、最初はこの機能のキーワードは then/kick という名前にしようと思ってたんですが、Ruby 2.6.0 から yield_self が then というaliasで突っ込まれたため(コミッターの人たちはthen then いいネーミングじゃないとRubyKaigiで言ってたのに、対案が出なかったんだな……)、このキーワードは諦めて chain/trigger になりました。


カテゴリー: 未分類 | コメント / トラックバック: 0個

Rubyを使って秒でBotを作るなら、秒でRedisだって使えなきゃ、やっぱり話にならないですよね?


このエントリは、 Mobb/Repp Advent Calendar の十一日目です

mobb-redis

昨日のエントリでは、MobbでActiveRecordを利用する方法を紹介して、Sinatraの資産をMobbが継承できるという話をしました。そして今日はRedisです。こっちは本当に秒でいけます。

https://github.com/kinoppyd/mobb-redis

まず、検証用のRedisを立てましょう。今ならDockerで秒です。

次に、mobb-redisをインストールします。例ではBundlerを使います。

最後にアプリケーションを書きます。

はい、秒ですね。起動して動作を見てみましょう。

このように、返答の中の時間が変化していないので、Redisを経由していることがわかります。

redis-sinatra

mobb-redisの元ネタは、redis-sinatraというgemです。

https://github.com/redis-store/redis-sinatra

これは、redis-storeというWebフレームワーク用のRedisラッパーを使って、SinatraにRedisを提供しているgemです。

昨日のmobb-activerecordと同じく、私がやったことはリネームだけです。このコミットを参照してください。

https://github.com/kinoppyd/mobb-redis/commit/aca774dd8d17f832d2ee6e9f3d7721a7fb314999

それどころか、このgemはforkしてから変更して動作確認してパブリッシュしてこのブログを書くまで、トータルで一時間かかっていません。資産を使うということは素晴らしいことです。

秒で作れるBot、秒で扱えるRedis

さて、gemを作るのも秒だったし、みんなが使えるようになるのも秒でした。素晴らしい。これからもどんどんBotを作るときにRedisを使っていきましょう。


カテゴリー: 未分類 | コメント / トラックバック: 0個

Rubyを使って秒でBotを作るなら、秒でActiveRecord使えなきゃ話にならないですよね?


このエントリは、 Mobb/Repp Advent Calendar 2018 の十日目です

ActiveRecord

ここ数日書いているエントリで、Mobbがいかに秒でBotを作れるすごいやつかというのは伝わったかと思います。しかし、世の中には秒で複雑で素敵なBotのアイディアが思い浮かぶ人もいます。そして、そういう人の多くは「いやいや、いくら秒でロジック書けても、DB使えないと話にならんでしょ」という人もいます。

そうです、Botのロジックは会話的なものが多いため、状態を記録したりデータを保持したりという行動を行いたいケースが非常に多いです。それでは、DBを使いましょう。RubyでDBといえば、ActiveRecordですね。ActiveRecord、使いたくないですか?

Mobbは、秒でBotを作るエンジニアを本気で応援するフレームワークです。当然、ActiveRecordも使えなくては話になりません。

安心してください、使えますActiveRecord。たった一つのgemを追加するだけで。

https://github.com/kinoppyd/mobb-activerecord

Usage

秒で使えるとは言いましたが、流石にActiveRecordは何も考えずに突っ込むことは出来ません。しかし、可能な限り何も考えずに突っ込めるようには準備をしています。

まず、bundlerを使っている場合は次のgemをGemfileに記述してください。使っていなければ普通にインストールしてください。

rakeはなくてもいいですが、あったほうが楽です。ここからさきはRakeがある前提で書きます。また、sqlite3のところは適宜好きなDBに読み替えてください。

その後、まずRakefileを作成して編集します。

5行目の require “./app” は、Mobbアプリケーションの名前がapp.rbであることを前提としているので、適宜変更してください。

次に、Mobbアプリケーションを作ります。

set :database の記述で、ActiveRecordにDB接続情報を渡しています。これはsqlite3の場合ですが、他のDBを使う場合は適宜必要な情報を渡してください。

ActiveRecordのクラスは、Userクラスを用意しました。これは、userというフィールドを持っています。

アプリケーションそのものは、add userという呼びかけでユーザーを追加したり、list usersという呼びかけで登録されてるユーザー一覧を見たりするものです。

それでは、DBを作成しましょう。まずRakeタスクで、DBの作成とマイグレーションファイルを作成します。

ここまでくると、もうRailsでよく見るやつですね。DBの作成と、マイグレーションファイルの作成を行います。その後、作成される db/migrate/xxxxxxxx_user.rb というファイルを編集します。

usersテーブルを作成し、nameというカラムを持つように定義します。

あとは、DBをマイグレーションしましょう。

無事マイグレーションされました。

それでは、アプリを起動してみます。

実際に操作してみましょう。

これは、Shellアダプタでadd userコマンドとlist usersコマンドを実行してみた例です。develpmentモードで動いているため、ActiveRecordのログが出力されているのがわかります。

以上が、MobbでActiveRecordを使うためのチュートリアルです。Rakeタスクの作成さえ行えば、あとはRailsでよく見る操作方法なので、特に迷うことも無いと思います。

sinatra-activerecord

mobb-activerecordというgemには、明確な元ネタが存在します。sinatra-activerecordです。元ネタどころか、mobb-activerecordはsinatra-activerecordのforkです。

https://github.com/janko-m/sinatra-activerecord

もともと、Sinatraにはsinatra-activerecordというSinatraでActiveRecordを使うためのgemが存在しました。そして、MobbはSinatraのエッセンスを完全に受け継いだフレームワークであり、特に拡張部分であるhelpers/extendsに至ってはSinatraと全く同じコードが書かれているということを数日前のエントリでも解説しました。

これはすなわち、SinatraのHTTP以外の資産を、ほぼそのままMobbでも活用できるということを意味します。実際、mobb-activerecordをforkしたとき、私のやった作業はSinatraという名前空間をMobbに変更しただけです。このコミットを見てもらえれば、本当にそのとおりだということがわかってもらえると思います。

https://github.com/kinoppyd/mobb-activerecord/commit/c46f31964a9903a44036b1dae96fba6858421ecc

これは本当に強力な資産で、Mobbは100%その恩恵に預かっています。

もちろん、HTTPの関心事の資産は流用できません。クッキーであったりセッションであったりという話や、CORSやCSRFのようなものは、Mobbの世界観にそもそも存在しないため、それらを助けるgemは流用してもなんの意味も持ちません。

その一方で、ミドルウェアをより使いやすくするgemは、まるごとそのままの恩恵をうけることができます。今回のActiveRecordがまさにその真骨頂です。

秒で作れるBot、秒で扱えるActiveRecord

さあ、この解説を読んだあなたは、もう秒でActiveRecordを使ったBotを書くことができます。無限のアイディアを、秒でBotに実装しましょう!


カテゴリー: 未分類 | コメント / トラックバック: 0個