秒でBotを作れるMobb、その裏にあるReppって何者?
このエントリは、Mobb/Repp Advent Calendar の2日目です
Reppというもの
昨日のエントリで、Mobbという秒でBotを作るための強力なフレームワークを紹介しました。このエントリでは、そのMobbを支えるReppという仕組みに関して書いていきます。
Webサービスを作ることに詳しいRubyプログラマ向けに説明すると、MobbとはSinatraであり、ReppとはRackです。それで理解できる方は、此処から先はそれ以上の情報が書かれていないので読む必要はありません。
ReppとはRackと言われてピンとこなかった方のために、Botを作るための複雑性を排除し、いかに秒でBotを作るかという話を交えつつ、Mobbに対してReppが何者を説明したいと思います。
Botを秒で作るために必要なもの
Botを秒で作るために必要なものは、複雑な物事をうまく隠す方法です。そしてSlackやTwitterを始めとした各種サービスとの結合部分は、その複雑な物事の最たるものです。
例えば、フレームワークに頼らず自前でBotを作るとしましょう。各サービスのSDKを探してきて、そのドキュメントを軽く読み、データの受取と投稿部分のコードを書きます。そしてその後、自分のbotが本当にやりたいロジックを書き、サービスと連携するコードと結合します。ドキュメント読むの、面倒じゃないですか? サービスと接続する部分の実装、面倒じゃないですか? こんなのでは秒でBotを作れません。
秒でBotを作るためには、サービスとの結合部分を徹底的に隠さなくてはいけません。サービスとの接続に手間取っているようでは、それは複雑も複雑であり、怪奇です。また、サービスとの接続部分は使いまわせなくてはいけません。あなたが作りたいBotは、一つではないからです。秒でBotを作るということは、毎秒新しいBotのアイディアを思いついて、すぐに実装する。そういうことです。
アイディアが閃いた? それすぐにSlackBotにしましょう。秒で。
この記事は、Slack Advent CalendarとMobb/Repp Advent Calendar 共通の1日目の記事です
Slack Bot を秒で作る方法
日常生活を送っていると、突然身の回りで何かが流行ることがありませんか? 私はよくあります。例えば、ある日突然絵文字をシャッフルすることが流行ったり、ちょっと昔には「Yo」と言ったら「Yo」と返すのが突然流行ったりしました。
何かが流行ったとき、それはbotを作るチャンスです。幸いあなたはプログラマで、流行った何かを更に面白くするアイディアとロジックは一瞬で頭に浮かびます!
けれど、そのロジックを実装するのは簡単ですが、実際にその機能をbotとしてSlackの野に放つのは簡単でしょうか? 私はあまり簡単ではないと思います。
例えば、多くの言語にはBot用のフレームワークが存在します。RubyであればRuboty、JSであればHubot、Pythonであればslackbot、JavaであればJBotなどが存在しますし、そもそも各言語のSlack用ライブラリが必ず存在するので、それを使えばフレームワークすら必要とせずにBotを作れます。
しかし、それらは本当に簡単でしょうか? 楽しいアイディアを思いついて、それをBot化するまでの間に情熱が冷めてしまうほど時間がかかりませんか? そして他の人が似たアイディアのBotをSlackに先に放ち、「俺の考えてたアイディアのほうが面白いのに、先を越された」なんていう愚痴をSlackで吐いたりするようなことになってしまいやしませんか?
Botのアイディアは、秒で形に出来なくてはいけません。そして毎秒クソボットを作れるくらいのスピード感が必要なのです。けど、そんなこと本当に可能なのでしょうか?
可能です。あなたがRubyプログラマであれば。
技術書典5にて、Sinatraのコード解説本を頒布します。あと執筆環境の話。
技術書典5に参加します
2018/10/08 に池袋サンシャインシティで開催される技術書典5にサークル参加します。すぺーすは「い22」です。よろしくお願いします。
頒布する本の内容は、ここ半年ほど作っていたMobbというボットフレームワークのコードを書くために読み込んだSinatraの、コードリーディングと解説の本です。Sinatraは非常にミニマルながらも強力な黒魔術と設計に支えられた、Ruby力を上げる教材としてとても良い題材だと思ったので、その知見を広く共有するために本にしました。
本の詳細と言い訳
頒布する本は、本文44ページで頒布価格は1000円にする予定です。これは、印刷した本の45%が売れた場合に、印刷代と参加費、その他諸経費がペイするように設定しました。45%の根拠は、同じサークルの檻総君が夏と冬に刷っている同人誌の売れ行きを観察するに、これくらいの数値設定が妥当だとおもわれるためです。
本文は、おおよそ二週間かからずに書き終わりました。しかし、その特急スケジュールのおかげで文字以外の図解などを作る暇がなく、やや読んでて疲れる本になってしまったという自覚があります。今回の技術書典でこの本を求めてくれる人が多ければ、もっと読みやすくした版を後日作成し、本を買っていただいた方には何かしらの形で無料で配れればと思ったりもしますが、その思いを実行できるくらい当日売れることを祈るばかりです。
Mobb 0.3 をリリースしました、これで実践的に毎秒クソボットをリリースできます
Mobb 0.3.0 をリリースしました
今回のリリースでは、 cron/every キーワードが Mobb DSL に追加されています。
https://github.com/kinoppyd/mobb/pull/5
これらのキーワードは、 Mobb(およびRepp) になにかを定期実行させるためのものであり、このリリースによってようやく Mobb は実用的で最も簡易な Bot フレームワークを名乗ることができるようになったと思います。
最初に Mobb を作ろうと考えたときに、秒でクソボットを作れるようになるために必要なものはなんだろうと考えました。そして、必要だと考えたのは、シンプルなシンタックスで書けること、条件分岐がフレームワークの機能に備わっていること、なにかを定期実行できること、の三点でした。 Mobb 0.3 のリリースにより、これら3つの要素が全て揃い、ようやく Mobb が実用的な Botフレームワークとして皆さんに使ってもらえる機能を備えました。
Syntax
cron/every キーワードは、名前の通りなにかを定期実行するためのものです。具体的なシンタックスは、次のように書きます。
cron '0 12-21 * * *', dest_to: 'times_kinoppyd' do
"Hi bro, wazup?
end
この例では、毎日12時から21時までの毎事0分に、botが times_kinoppyd という配信先(Slackであれば #times_kinoppyd) に対して、 “Hi bro, wazup?” というメッセージを配信します。
cron キーワードは、そのまま Cron の文法が使えます。具体的には、 parse-cron というGemを使ってパースできるものであれば動作します。
また、 cron の簡易的な使い方を目的として、 every というキーワードも追加しています。
every :day, at: '14:00', dest_to: 'times_kinoppyd' do
"ぞい"
end
cron よりも、より直感的に定期実行を記述できます。これも、 whenever というGemを内部で利用しており、 whenever が解釈できる引数をそのまま渡すことができます。例えば次の例では、2というIntegerオブジェクトへのWheneverの拡張により、hoursというメソッドが追加されていて、それをそのまま使用することができます。
every 2.hours, dest_to: 'times_kinoppyd' do
'ぞい'
end
every キーワードが受け取る事のできるシンタックスに関しては、 whenever のREADMEを参考にしてください。
注意点として、 cron/every キーワードでは、必ず dest_to というコンディションが必要な点に注意してください。これを設定しないと、Botはどこのチャネルにポストすれば良いのかがわからず、メッセージが闇に消えることになります。(Shellアダプタなど、チャネルの区別が無い環境であれば必要ありません)
Mobb 0.2.0 out now
Mobb 0.2.0 をリリースしました
このバージョンでの大きな変更点は、helpersメソッドの追加とconditionメソッドの追加です。それぞれ、Mobb DSLのベースとなっているSinatraでは非常に大きな役目を果たしていたけれど、まだ移植が終わっていなかった機能です。
https://github.com/kinoppyd/mobb/releases/tag/v0.2.0
新機能の解説
0.2.0では、 helpers と condition が大きな変更点です。
helpers
helpersメソッドは、onイベントの中でアクセス可能なメソッドをMobbアプリケーションのトップレベルに定義するためのメソッドです。
require 'mobb'
helpers do
def greet(name)
"Hello #{name}"
end
end
on "Hello" do
greet(@env.user.name)
end
このように、 on メソッドのブロックの中でアクセスできる greet メソッドのような、ヘルパーと呼ばれるメソッドを定義します。
本来、 on メソッドのブロックは、この記述の場合は main オブジェクトに対するクロージャになっています。しかし、MobbやSinatraは内部でこの binding を書き換えるトリックを使っており、通常の手順では main オブジェクトに定義したメソッドや値を参照できません。例えば、次のコードは実際にブロックの中が呼び出される時にエラーになります。
require 'mobb'
def yo
"Yo"
end
on 'Yo' do
yo # error on runtime!
end
そのため、 helpers というメソッドを使い、一時的に self を on メソッドのブロックが実行される時と同じスコープに書き換えます。そして、そのスコープ内で def を使ってメソッドを定義することで、 on のブロックからアクセスできるヘルパーメソッドを定義することができるのです。
技術書展5で、Sinatraのコードリーディング本を頒布します
技術書展5に当選しました
当選してしまったので、技術系の同人誌を書こうと思います。
最近の個人的な活動の成果として、SinatraのDSLっぽくBotが書けるMobbというプロダクトを作っています。そしてその過程で得たSinatraのコードリーディングの知見を広く共有するのが良いだろうと思っているので、同人誌にしようと思います。
たまたま、今日の夕方に会社のイベントでこういう発表をしたのですが、これを更に詳細に掘り下げた本になると思います。
kinoppydさんの「Sinatra trick」 / kinoppyd さん - ニコナレ
スペースはどこ
◎貴サークル「トレイリア学園」は、 い22 に配置されました。
どういう本にするの
Sinatraの全コードの解説を目指します。一冊読めば、自分でSinatraライクのDSLを書けるようになるくらいの本にしたいと思っています。対象読者層は、メタプログラミングRubyを読んだことがあるRubyエンジニアを想定していますが、それではあまりにもリーチが狭い(気がする)ので、メタプログラミングRubyの本を読んでいなくても簡単なエッセンスくらいは理解できる感じに解説を入れようと思っています。
また、現在コミットをしているMobbというBotフレームワークのプロジェクトに関しても、簡単な解説本を書こうと思っています。
追伸:Rubyのサンドボックスを作って、evalするBotを作った
注意:安全じゃないです
あらすじ
-
入力されたRubyのコード文字列を安全にEvalするBotを作ったと主張する
-
次々と安全ではないことがわかる
-
ちょっとずつ安全に向けて改良したが、まだまだ安全じゃない
詳細はここ↓
http://tolarian-academy.net/ruby-sandbox-eval-bot/
たくさん届いた指摘
前回の最後の追伸から一夜明けて、またいくつかの指摘を頂いた。それぞれに関して対策を講じていく。
Rubyのサンドボックスを作って、evalするBotを作った
注意:安全じゃありません
RubyのSnadbox環境
Sansbox環境とは、外部から入力されたプログラムを安全に実行する環境のことです。任意のコードを入力可能な場所で、いきなり system(“rm -rf ~/) とか入力されて、それが本当に実行されたら困りますよね? 自分は困ります。ですが、外部から入力されたコードを安全に実行する環境というのはそれなりに需要があり、最もわかりやすいところではJavaScriptを実行するブラウザ、わかりにくいところでは今回作ろうとしているeval用のbotです。
ブラウザに関しては、インターネットという非常に治安の悪い場所から送られてくるコードを自分の環境で実行するので、サンドボックスが必要です。同じように、会社のSlackで公開するRubyの任意のコードを実行してくれるBotでも、社内の邪悪な人から投げ込まれたコマンドで自分の環境を破壊されると困るわけです。競技プログラミングの採点サーバーなどで、送られてきたコードを実行するときに邪悪な奴だったら困りますよね? そういう邪悪なコードから身を守るために、サンドボックスは必要です。
その一方で、Rubyは結構自由すぎる言語で、任意の入力に対する安全なコードの実行というのはなかなか難しいです。Rubyには、汚染マークとセキュリティレベルという、外部からの入力を安全に扱う機構があり、これはメタプログラミングRubyでも紹介されています。詳細はリンク先のページを見てもらえると解りますが、しかしこの機構には一つ大きな問題があり、それは汚染された文字列をevalすることができないということです。そのため、汚染された文字列を自分の手で安全だとマークしない限り、evalの引数に渡して実行することができないのです。それはつまり、セキュリティレベルで保護されている内容と同じレベルの安全性であることを自分で保証しなくてはならないということです。それって、セキュリティレベルを自分でもう一度チェックしなくちゃいけないということなので、ハッキリ言って意味が無いです。
なので、セキュリティレベルに頼ることなく、危険なコードを事前に実行できないように、サンドボックス環境を用意する必要があります。
Ruby製の軽量Botフレームワーク Mobb をリリースしました
TL;DR
Sinatra-isiに記述できるBotフレームワークMobbと、Botとサービスの間を取り持つRackに相当するReppを作って公開しました。
require 'mobb'
on "Hello" do
"Hi!"
end
作ろうと思った理由
RubyでBotを作ろうと思うと、大体の人はRubotyを選択すると思います。GoogleでRubyを使ってSlack Botを作る方法なんかをいくつか探していると、そのうち自然とRubotyに行き着くと思います。ドキュメントやサンプルも日本語で多数用意されており、日本のRuby界隈におけるBotの選択肢としてはデファクトスタンダードではないかと思っています。
Rubotyはとてもよく作られたBotフレームワークですが、個人的にはプラグインのロードにBundlerを使って$LOADPATHから順次プラグインをロードしている点がやや使いづらいと感じていました。公にできない自作プラグインや業務レベルのロジックを乗せるには、それを回避する手順が煩雑になってしまい、Rubotyのロードの仕組みを調べることで本来やりたかったことに使うべき時間を奪われてしまうのが悩ましいところでした。
また、RubotyはHandlerとActionというクラスをきちんと定義しており、その流れに乗っていれば特に迷うことは無いのですが、本当にどうしようもないほどにくだらない機能(例えば、渡された言葉をシャッフルしてechoするだけのしょうもない単機能のbot)しか持っていないbotを作るには、あまりにもリッチ過ぎる制約を持っていました。また、プラグインのロードの都合上、Handlerは自分たちのロジックとは違うモジュール名空間に置かなくてはいけないことも気になる点でした。
この煩雑さを回避したい、毎秒クソボットを作りたいと思うようになると、まるでRailsのようなRubotyの壮大な世界観が少し扱いづらくなってきました。そこで、この悩みを解決するためにSinatraのような手軽さで書けるRuby製Botフレームワークが必要だと感じたために、Mobb プロジェクトを開始しました。
また、世の中には数多のBotフレームワークがありますが、その殆どが自らのコードに特化したサービス接続用のプラグインを持っており、他の世界観で使い回すことができないことも大きな問題だと思いました。これはちょうど、Web ApplicationとWeb Serverの接続部分を解決したRackのようなものがBotの世界にも必要だということであり、Rack(のような)インターフェイスにしたがってBotエンジンを記述すれば、サービスとの接続部分を使いまわせると考えたことが、ReppというBot用の共通のサービス接続インターフェイスを作ろうと思った動機です。
RubyKaigi2018のCFP落ちたので後学のために置いておきます
タイトルの通り、RubyKaigi2018のCFPに送ったプロポーザルがリジェクトされたので、内容を公開して後学のためになればと思い、自らの反省点込で残しておきます。
送った内容
Title
Reading Sinatra to build Sinatra-ish application
Abstract
Reading Sinatra code is the best way to learn how to write minimal daemon program by Ruby. Sinatra was made only of about 2000 lines Ruby code. However, the codes consist of skilled Ruby codes, fundamental metaprogramming techniques, and server programming techniques. In this session, first I will introduce how I made a Sinatra-ish Bot daemon program. After that, I will explain the whole Sinatra code in 40 minutes.
Details
Rubyのライブラリを作成/OSSに貢献するにあたって、Rubyの初心者が次の一歩を踏み出すためのトークをしたいと思います。そのためにまず、有名なライブラリのコードを読むことで、最初の一歩の手助けをしたいと思います。 このトークでは、まず最初に私が作成しているSinatraライクなDSLでBotを作るライブラリを紹介します。 その後、そのライブラリを作成するためにSinatraをどう参考にしたかを解説するため、Sinatraの約2000行のすべてのコードリーディングを40分のセッションの中で目指します。 このトークによって、聴講者はSinatraのコードを読んだという実績と、Ruby製のライブラリへの構造への理解を得て、Rubyによる開発によりいっそう親しめるようになることを期待しています。
Pitch
多くの人がRubyのコードを書くことに親しみ、OSSへの貢献を目指します。 また、Sinatraのコードをまるまる読んだことがある人は意外と少ないと認識しているので、中級者にも新鮮な体験になるのではないかと思っています。
反省点
いくつか思いつくので、箇条書きにします
自分が作ったもののアピールポイントが少ない
Ruby25thイベントで、「RubyKaigiは自分が作ったものを自慢できるような場であればいい」の旨の発言を、どなたがなさっていたのかは失念しましたがされていたように記憶しています。
その中において、私がプロポーザルに書いたメインは「Sinatraのコードリーディング」の部分で、私が書いたBotのフレームワークに関する話はサラッと流れています。この次の反省点とも関連するのですが、Sinatraをメインに置くのではなく、自分のプロダクトをメインに(例えば、私のコードがどのようにSinatraから影響を受けたのかなど)して送るべきでした。
特定のアプリケーションの話をしようとしている
私がRubyKaigiに参加したのは2016年からですが、2回参加しただけでも、RubyKaigiにおける「特定のアプリに関する話はせず、Rubyの話をするべき」という空気は強く感じ取っていました。
その中に置いて、Sinatraという単一のプロダクトに関するコードリーディングをしたいというプロポーザルは、かなり無理筋だったような気がします。
まだ作っている途中のプロダクトに頼ってプロポーザルを送ってしまった
プロポーザルの中に書かれたBotフレームワークは、まだ開発中です。アイディアレベルから、一応動くかな? くらいの実装までしかまだ完成していないため、コードはGithubのパブリックリポジトリにはプッシュしていません。
そのため、まだどこにもコードが無いプロダクトに対して、レビュアーも評価の下しようがなかったように思います。自分が作ったプロダクトの話をしたいなら、せめてそのプロダクトのコードなりLPなりDocなりをもってこいと言われて然るべきだと思います。
実際、プロポーザルを送ってからも開発を続けていたところ、「これはプロポーザルに書くべきだったな」と思うようなコードやアピールポイントがどんどん見つかっています。まだアルファ版も完成していないプロダクトでのプロポーザルは、無謀でした。
DetailとPitchが日本語
これはそんなに気にしていなかったのですが、過去にCFPを送って通った方のブログを見ると、RubyKaigiは国際カンファレンスなので、すべて英語で送ることが望ましいという旨のことを記している方が多いように思われます。なので、これは手抜きせずにきちんと英語で送るべきだったと思いました。
今後
反省点をだいたいまとめると、「まだ完成も公開もされていないプロダクトの話を、特定のアプリケーションのコードリーディングでごまかすようなプロポーザルを送ったのが失敗」だったと思います。そのため、来年はきちんと完成したものを元にしてプロポーザルを送るべきでしょう。
めげずに来年もCFPに出せるようにがんばるので、がんばります