generatorでhubotを対話式にする


Node.jsの0.12でES6のgeneratorが使えるようになっているので勉強。
正しい使い方かわからないが、hubotで使うと対話モード風になる。

コード

calendar.coffee
module.exports = (robot) ->
        cal = ->
                title = yield 'Creating a schedule. \nWhat?'
                attendees = yield 'With whom?'
                date = yield 'When?'
                while date == 'sat' or date == 'sun'
                        date = yield 'No meeting on weekend. When?'
                place = yield 'Where?'

                delete robot.cal
                'Set ' + title + ' with ' + attendees+ ' on ' + date + ' at ' + place  + '. Enjoy!'

        next_value = (input) ->
                robot.cal.next(input).value

        # respond block
        robot.respond /cal$/i, (msg) ->
                robot.ignore_input = true
                robot.cal = cal()
                msg.send(next_value())

        # hear block
        robot.hear /(.*)\s*$/i, (msg) ->
                if !('cal' of robot and robot.cal)
                        return
                if 'ignore_input' of robot and robot.ignore_input
                        delete robot.ignore_input
                        return
                msg.send(next_value msg.match[1])
  • generatorはrobot.calとして登録されている。
  • yieldでgeneratorから呼び出し元に処理が移る。
  • generatorが存在する間はhearのたびにgeneratorのnextが呼び出される。

結果

コンパイル

hubotの挙動確認としては不要だが、初めはコンパイルしながら確認したほうがよいかもしれない。
http://coffeescript.org/ のTRY COFFEESCRIPTでもできるし/path/to/coffee -c scripts/calendar.coffeeでも可能。
yieldを入れておくと自動でgenerator(function*)が生成されるのがミソ。

感想とか

  • ステートマシンっぽくステートとそのネットワークをうまく管理するのがよいのだろうが、ステートにいちいち名前つけてフロー修正を試行錯誤するのは面倒
  • yieldnextなら、ただの入力待ちのような感じで使えて手軽
  • generatorの保存はrobot.brainでもがんばればできそうだったが、プロセス再起動で確実に挙動を切り替えてほしいのでとりあえずrobotのメンバーとしてみた(永続化するほどのデータは扱わない前提)
  • コードの臭い
    • respondhearが複数マッチする場合、コードに書いた順に呼ばれる。書く順を変えると破綻する
    • respondの直後にhearも呼ばれるがそのままreturnしてほしい、みたいな余計な意図のコードが入る
    • 手戻りややり直しが数回程度の簡単な状態遷移であれば見通しよくコンパクトに収まるが、気がついたらモンスター化してそう
  • ステートパターン上手く使えないかな?

環境構築

本題ではない。使ったのは以下のバージョン。

実際にgeneratorを利用できるようにするところで四苦八苦。
バッドノウハウっぽいが動いているので、ちゃんとした手順にするのはいったん後回しにする。

  1. 使っているcoffeeの実行ファイルを探す。
  2. coffeeのshebangでのnode呼び出し時に--harmonyオプションを渡したいのだが、shebangではファイル名以外の引数が渡せなかったので間接ファイルを作って呼び出す。
$ vi /path/to/node-harmony
#!/bin/bash
/path/to/node --harmony "$@"


$ vi /path/to/coffee
#!/usr/bin/env /path/to/node-harmony

(もちろんパス通してもよいと思う)

参考にした情報