はこべブログ ♨

ソフトウェア開発のことやアニメのことを書いてることが多いです

golangで書かれたSlack bot でエンジニアに話題提供しよう

こんにちは、id:hakobe932 です。はてなエンジニアアドベントカレンダーの18日目として、はてな社内で導入をためしている話題提供Slack botの機能と実装について紹介します。昨日はid:astj による Herokuとwerckerによる継続的インテグレーション・自動デプロイでperlのwebアプリケーションを開発するでした。

#enginnerで技術交換

はてなではメインのチャットツールとしてSlackを活用しています。チームや職種などの単位のたくさんのチャンネルがあり、それぞれのチャンネルでコミュニケーションが行われているのですが、もっぱら技術的な議論を行っているのが #enginner というチャンネルです。#engineer では、チームをまたいだ技術的な相談のほか、新技術や勉強会の紹介など、技術に関わるさまざまな話題で情報交換しています。

もっと技術の話題でわいわいしたい

基本的にはにぎやかな#engineerですが、どうしても業務で利用している技術の話題に偏りがちです。社内のエンジニアが興味があって調べている技術や、専門にしている技術などを自然に話題に取り上げられないかと考えていました。

engineerkun登場

そこで、最近はengineerkunという名前の、自動的に話題を提供してくれるSlack botを#engineerに常駐させています。engineerkunは、はてなブックマークは指定したタグで検索して、適度に人気で新鮮なエントリのURLを定期的に発言してくれます。

f:id:hakobe932:20141216085704p:plain:h400

単に定期的にURLを発言するだけだと人間の会話の邪魔になるので、以下のように適度に遠慮して活動するのが特徴です。

  • 人間が会話している時には遠慮して発言しない
  • 人間が会話していなくても連続で数回発言したらだまる (朝来たらログがうまっているということがない)

会話が途切れた時に適度におもしろURLが投稿されることで、多様な話題によるコミュニケーションがわいわいと起こることを期待しつつ動かしてみています。

engineerkun の使い方

engineerkunはbotの愛称で、実装は拙作のpresentというツールです。presentを使うと、engineerkunのような話題提供botを簡単に作ることができます。


hakobe/present · GitHub


golangで書かれているので、go getコマンドを利用するとすぐに利用することができます。

$ go get github.com/hakobe/present
$ PRESENT_SLACK_INCOMMING_URL="https://hooks.slack.com/services/ABCD1234/EFGH5679/abcdefghijk123456" \
  PRESENT_DB_DSN="id:pass@tcp(mysqldhost:3306)/dbname?parseTime=true&charset=utf8" \
  PRESENT_NAME=engineerkun \          # コマンドを実行するときに呼ぶbotの名前
  PRESENT_WAIT=900 \                  # URLを発言する頻度(秒)
  PRESENT_NOOP_LIMIT=3 \              # この回数だけ連続して発言したら一時停止する
  PORT="8080" \                       # WebHooksを待ち受けるHTTPサーバのポート
  $GOPATH/bin/present

presentは、発言用とチャンネルのログの監視のためにSlackのIncomming WebHooksとOutgoing WebHooksを一つづ利用します。また、タグや検索したURLを保存するストレージとしてMySQLが必要です。くわしくはREADMEで説明しているので参照してください。

heroku-buildpack-goを利用すると Heroku 上でも動作させることができます(適宜Godepsをリポジトリに含める必要があります。heroku-buildpack-goのドキュメントをご参照ください。)。

チェックするはてなブックマークのタグは、botを常駐させたチャンネルでengineerkun add perlのように発言すると追加することができます。例えばはてなでは以下のようなタグを設定しています。

f:id:hakobe932:20141218011016p:plain

この状態で放っておくと、環境変数で設定したPRESENT_WAIT秒後に、設定したタグに関する人気記事がbotを常駐させたチャンネルに投稿されます。

f:id:hakobe932:20141218011012p:plain

人間が会話している時には、botが適当に遠慮してURLが投稿されませんので、動作を検証したいときにはengineerkun plzのようにお願いするとよいでしょう。

f:id:hakobe932:20141218113311p:plain

もちろん、技術に関係のないタグを登録することもできますから、engineerkun add animeのようにして、ひたすらアニメ情報をウォッチするのもよいでしょう。

そのほかの機能については、engineerkun helpと発言するか、READMEを参照してください。

実装の紹介

presentgolangを使って実装されています。ほとんど標準ライブラリのみを利用しています(MySQLのドライバだけはgo-sql-driver/mysqlを使っています)。いくつかおもしろポイントを紹介します。

goroutineを活用した設計

はてなブックマークを定期的にチェックするgoroutineSlackのOutgoing WebHooksを受け付けるWebサーバのgoroutine、そしてそれらを管理する スケジューラのメインルーチン の3つが協調して動作します。

それぞれのgroutineは自分のメインループを持ち、各々自分の仕事しています。goroutineの外部へのインターフェースとしてはchanelを公開するようにします。goroutineの中ではプログラムは逐次的に動作しておりデータ競合について考慮する必要はありません。たとえば 、はてなブックマークを定期的にチェックするgoroutineでは、概ね以下の様なメソッドをつかって新しいgoroutineを作っています。

func Start() (<-chan *RssEntry, chan<- []string) {
	ticker := time.Tick(10 * time.Minute)
	out := make(chan *RssEntry)
	newTags := make(chan []string)

	collect := func(tags []string, out chan *RssEntry) {
		...  
		out <- fetchedEntry // 結果をチャンネルに返す
	}

	go func() {
		tags := make([]string, 0)
		for {
			select {
			case <-ticker:
				collect(tags, out)
			case ts := <- newTags: // 新しいタグの一覧を受け取る
				tags = ts // 逐次的に動作するので同期化せずに状態を変更できる
				collect(tags, out)
			}
		}
	}()

	// インターフェースを公開する
	return out, newTags
}

このような機能を実装する場合、オブジェクト志向プログラミングでは、機能の持つ状態を抽象化したオブジェクトを定義することが多いですが、golangではプロセス( = goroutine)を使って処理を中心に機能を抽象化することができます。並行処理と相性が良く、Erlangのようなプロセスを中心にプログラムを行う言語ではよく利用されている方法です。

DB操作

golangの標準ライブラリである、database/sqlを使うとRDBMSに接続してSQLを実行することができます。コネクションプーリングを備えていたり、goroutineをまたいで使っても競合が起きない(= groutine safe)になっているなど、golangらしい特徴も備えます。素朴にSELECT文を実行すると以下のようになります。PerlDBIのようにシンプルです。

func All(db *sql.DB) ([]string, error) {
	sql := `
		SELECT tag FROM tags ORDER BY tag ASC
	`

	rows, err := db.Query(sql)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	tags := make([]string, 0)
	for rows.Next() {
		var tag string
		if err := rows.Scan(&tag); err != nil {
			return nil, err
		}
		tags = append(tags, tag)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return tags, nil
}

MySQLに接続するにはgo-sql-driver/mysql · GitHubが必要です。PerlDBIと同じようなDSNを設定して接続するのでPerl使いにも馴染みがありますね。

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)
db, err := sql.Open("mysql", "id:pass@tcp(mysqldhost:3306)/dbname?parseTime=true&charset=utf8")

まとめ

以上 engineerkunをその実装である present の紹介でした。presentを使うとエンジニア向けに限らない話題提供botを気楽につくることができます。 はてな社内での評判はまずまずですが、だいたいうまく機能しているのでぜひおためしください!

はてなエンジニアアドベントカレンダー の明日の担当は id:wtatsuru さんです!

はてなでは、golangやエンジニア同士の交流の仕方に興味のあるエンジニアを募集しています。

関西2014年秋アニメ 放送時間まとめ

今期も関西における今期のアニメの放送状況を表にまとめました。いつもどおりしょぼいカレンダーのデータを利用させていただいています。ありがとうございます。予約設定時の確認などにお役立てください。

今期の関西最速は三作品となりました。

なんといっても、Gのレコンギスタが関西最速なのがうれしいところです。いうまでもない注目作品ですから、十分に最速を味わいながら視聴しましょう!前作がとてもおもしろかったガンダムビルドファイターズトライも放送され、今期はガンダムの新作が同時に二作も放送されるといううれしい事態になっています。

神撃のバハムート GENESISは同名のソーシャルゲームが原作のアニメーションです。僕は原作はほとんど知らないのですが、監督がTIGER & BUNNYさとうけいいちさんとのことで期待できそうです。PVを見るとダークファンタジーの世界という感じですね。

結城友奈は勇者であるは原作なしのオリジナルアニメーションです。サイトのドメインがかわいいですね。サイトを見ると舞台が勇者部であったり、主人公がどうやら変身したりするらしいことはわかるのですが、くわしいところははっきりしません。一見、日常系ゆるふわアニメですがオリジナルアニメーションですから、油断せずに行きましょう。

今期は個人的にも注目作品が多く、たのしい期になりそうです。それでは関西のみなさん今期も頑張りましょう。


社内技術勉強会でScalaのおすすめポイント解説した

はてなでは週に一回、社内技術勉強会というのをしています。今週は僕の当番だったのでScalaの入門的な話をしました。

普段使いの言語として、Scalaの便利なところをまとめたというつもりです。とはいえ、他の言語にもある特徴もわりと紹介してるので、もうちょっとScala独自の内容にフォーカスしてもよかった... むずかしい。時間の都合で全部話きれなくて、会が終わった後でimplicitまわりの話とか数人にご紹介したら一番おもしろかったと言う話になったので無念。

あの機能を紹介してないとはけしからんみたいなのがあったら教えて下さい。そうはいってもとりあえず
Scalaスケーラブルプログラミング第2版を読むといいです。



↓ 資料はgist形式でembedしてあります ↓


https://gist.github.com/hakobe/e1aa2501a64e7f801b55

こちらもおすすめ

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

AnimeKansaiをHeroku化した

ずいぶん昔に関西のアニメ放送情報を10分前に通知してくれるAnime KansaiというTwitter botを作った。


Twitter/AnimeKantoがうらやましかったのでTwitter/AnimeKansaiを作った - はこべブログ ♨

このTwitter botは自分のVPSで動かしてて、10分ごとにcronで起動されているという仕組みだった。結構便利に使って頂いてるのだけど、TwitterAPIの仕様が変わるとか、うちの怠慢とかがあって、よく止まってしまっていた。

これまでは、スクリプトが自分しかアクセスできないVPSに置いてあって、とくにリポジトリで管理されてなかったのでとりあえずコード変更がしづらかった。cronでちまちまうごかしているというのも、プログラムの様子を調べずらくて厳しかった。VPSをたまに引っ越すときとかにも移管が地味に面倒だった。

ということでAnimeKansaiのコードをHerokuで動くbotとして書きなおした。今どきはHerokuの無料枠でbotをうごかし続けられるので大変便利。開発やDeployも気楽にできるしログの様子も簡単に見れる。もともとはPerlで書かれたすぐに終了するスクリプトだったけど、Node.js + CoffeeScript で常時起動するスクリプトに書き換えてcronいらず。

READMEにも書いてあるけど、任意の地方のアニメ放送情報をツイートできるようになってる。しょぼいカレンダーのアカウントとbot用のTwitterアカウントがあればだれでもbotをDeployできるはず。以下のボタンからご利用ください。

ボタンを押すとHerokuのログインしたら以下の様な画面が出てくるの環境変数を入れたら利用開始できるのでお手軽。bot用のTwitterアカウントでTwitterアプリケーションを作って、管理画面のAPI Keysタブから Create my access tokenすると、楽にaccess tokenが取得できるでしょう。

f:id:hakobe932:20140907122507p:plain

Scala in Perl Company という内容で発表しました #yapcasia【YAPC::Asia Tokyo 2014】

Scala In Perl Company : Hatena - YAPC::Asia Tokyo 2014

Scala in Perl Company という内容で発表しました。はてなではPerlを10年近く利用していますが、最近作っているMackerelというサービスの開発ではScalaを利用しています。この発表では、Perlによるソフトウェア進化の困難さと、Scalaでその課題がどのように解決できるかについて解説しました。

発表時間がたりず、ScalaでのWebアプリケーション開発についてはくわしく紹介できなかったのですが、資料にはその部分も含めています。雰囲気でもつたわればうれしいです。話しきれないところがいろいろあったので、まだ会場にいる方はぜひ声をかけてください!



【予告】YAPC::Asia Tokyo 2014 で話します

Scala In Perl Company : Hatena - YAPC::Asia Tokyo 2014

明日の昼、13:40から20分となっております。

最近はてなScalaのプロジェクトをはじめてるので、なんで別言語をはじめたのかとか、なんでScalaを選んだのかとかについて話します。時間がありそうなら、Scalaで書かれたWebアプリケーションをどういう感じで作っているかという話もします。

プログラミング言語の基礎概念の練習問題を解くプログラムを作った(EvalML3まで)

プログラミング言語の基礎概念を学んでる - はこべブログ ♨ の続きです。

前の記事では、プログラミング言語の基礎概念という本を紹介した。この本では学んだことを確認するために、S(S(Z)) + Z evalto S(S(Z))のような式が正しいことを、与えられた推論規則にもとづいて導出するという練習問題が用意されている。しかし、本の中盤くらいの問題から人間ががんばって手で解くのが辛くなってくる。( 例えばこういうのを丁寧に書かないといけない https://gist.github.com/hakobe/860fd1dd9c56c1d33a33 )

前回の記事についたブックマークの中で、id:OKU_s62 さんが以下のようにおっしゃていた。

後半の問題の導出木を書くのは人間業ではないので、導出木を生成するプログラムを書きましょう

http://b.hatena.ne.jp/OKU_s62/20140714#bookmark-205135689

なるほど、たしかに。ということで、問題を解くプログラムを作ってみた。本の中で説明されているEvalML3という言語の式を、したにあるtextareaに与えると導出木を作って出力してくれる。出力を、本の演習システムにコピペすると正解という判定になるはず。

下のtextareaの中身を適当に変更すると導出木も変わるのでおためしあれ。サンプルにいくつかEvalML3の式をリストしてあるので、コピペするとためせる。

  • y = 3 |- let add = fun x -> y + x in add 5
  • |- let twice = fun f -> fun x -> f (f x) in twice twice (fun x -> x * x) 2
  • |- let rec fib = fun n -> if n < 3 then 1 else fib (n - 1) + fib (n - 2) in fib 5

人間がやると辛い問題もすばやく結果が返ってきて、ありがたい。フィボナッチ数を計算してる式とか絶対手でやりたくなかったし助かった。

CoffeeScriptでわりと雑な感じに実装したけどおおよそ動いてそう。ソースコードは、hakobe/copl · GitHubにおいてある。

実装は、入力した式をパースしたあと、ASTをたどりながら計算を実行しつつも同時に導出木を作る、という風になってる。ほぼインタプリタを作ったと言う感じになった。本には、パーサを書くための厳密なBNFは書かれていないので、自分で適当にBNFを考えてパースするのだけど、BNFを見ただけでLL文法なのかLR文法なのかを判定する知識がなかったので困った。id:tarao 先生曰く、LALRのパーサがあれば十分なはずとのことだったので、jisonというJavaScrip用パーサジェネレータを使うことにした。jisonはCoffeeScriptをパースするのにも使われて実績があって、ほぼbisonと同じことができて高機能。文法は、こういう感じにに書ける。

実は、今定義してある文法にはshift/reduce conflictが残ってる。例えば、パーサが1 + f まで読んで、次のトークンが3だったときに、1 + f でreduceするか、1 + f 3 のようにみなすためにshiftするかが曖昧になっているっぽい。これはEvalML3の関数適用の文法が原因なのはわかってる(このへん)。結局解決方法はわかってない。衝突した場合はshiftが優先になるらしく、意図どおりなので放置してるけど、なんとかしたいという気持ちは残ってる。

本題とは関係ないけど、もともと手元のNode.jsで動かしてためしていたコードを、browserifyでブラウザ上で動かすというのもやってみた。gulp, TypeScript, Browserify で Chrome 拡張を書く - 詩と創作・思索のひろば (Poetry, Writing and Contemplation)がともても参考になった。構文定義をコンパイルしたり、coffeeのファイルをコンパイルしたりというのもあるので、gulpでビルドの仕方を記述してみている(このへん)。

本はまだ途中で今後少しづつ言語が拡張されていくのだけれど、それに合わせてこのプログラムも拡張していけば、演習問題も余裕で解けるはず。人間がコンパイルするのはまちがってるので、自動化成功してよかった。