YAP(achimon)C::Asia Hachioji 2016 mid in Shinagawaに行ってきた

7月2日(土)、3日(日)の2日間開催されたYAP(achimon)C::Asia Hachioji 2016 mid in Shinagawa(以下ヤパチー)に行ってきました。

yapcasia8oji-2016mid.hachiojipm.org

トークした

実はこういうイベントに参加するの初めてで、まートークしたことも当然ないです。が、同僚のしんぺいさん(id:nkgt_chkonk)に誘われ、「応募する〜する〜」とか雑な返事をし、社内リポジトリに:

f:id:takkkun:20160705162740p:plain

こういうIssueが立てられ、「いっちょやってやるか」と雑な意識を奮い立たせてですね、応募しました。で、採択されました。

発表の内容はこんな感じ。

speakerdeck.com

普段会社でやっていることを発表しただけです。が、まとめるとなるとそれなりにパワーがいるもので、結構尻込みしたりするものです。そういう意味でしんぺいさんからの後押し(業務命令じゃないぞ!いやほんとうに)だったり、「楽しいことやるんだぞ!」という雰囲気があったのはとても良かったと思います。

なんというか、ややエモ話になるんですが、やったことないと大丈夫かなアハ〜ンとか思ったり、怯えたりとかで、見送る、見送り続けるってよくある話だと思うんです。ですが、同時に「このままでいいのかな」みたいな焦りもあるわけです。人と話すたびに「なんだこれ知らない……。知らなさすぎることがありすぎる……。大丈夫なのかこのままで私……」みたいなね。

ただそのまま何もしないと、そう思い続けるだけで。最悪なのは1, 2日経つとそんな感情まで忘れてしまうところですよ。適度に褒められずにやっていけるほどマッチョでもないし、かといって何もしないと褒められないしで、要は人に見える形で行動を起こせみたいな自己啓発本みたいなことを言うわけですが。

とまあそんな中途半端なところに火を点けられたので、よかったです。やってよかった!!!!!!ばんざい!!!!!!!これからも熱量は上げていきたい所存。

ちなみに発表の内容に対して、発表後や懇親会で聞かれたことのひとつに「これ後からフィールド(プロパティ)の内容書き換えたら意味なくね?(要約)」があるんですが、これ後でエントリーにします。このブログに書くかもしれませんし、社のブログに書くかもしれません。ここに書こうかと思ったが、ちょっと量が多くなりそうなんだ!

もうひとつトークした

当日はほとんど飛び入りトラックが行われる部屋に居ました。飛び入りトラックは当日までタイムテーブルが空っぽで、当日「これを喋るぞ!喋らせろ!」とかいう要請の元タイムテーブルを埋め、その時間が来たら発表してもらう、ってやつなんですが、そこでもトークしました。まあただの漫談なんですけど。なのでスライドはありません。

内容はしんぺいさんと「Scalaとか設計とか」について話すというもので、とか言いつつ大体は「immutableとmutableの差による設計への影響」みたいなところが焦点だった気がします。なんというか、いつも会社で話していることの延長線ですね。

スタッフした

ほとんどを飛び入りトラックの部屋で過ごしたのは、そこのスタッフだったからです。もちろんスタッフ経験も初めて。初めて尽くしですね。

他の部屋の雰囲気はそんな掴めていないのですが、飛び入りトラックの部屋はそれらに比べて大分規模が小さく、その分トークする人とそれを聞く人の距離が近くて良い雰囲気でした。特に id:kksg さんのErgoDoxの話は楽しかった。実際に触らせてもらえたし、触ったら全然今までの感覚が通用しないし、それが面白すぎるし、マウスのくだり(マウスからの入力に対してErgoDoxで「マウスのカーソル動かせますよ」「動かせんの!?」というやつ)はコントかよって感じで笑ったし、本人は訪問販売とか言ってましたが、まんまと欲しくなりました。いや、本当に買おうかな……。

まとめ

まとめというか、だいたい「圧倒的感謝」みたいな話になって、「おいおいあいつ多幸感に包まれてそのまま……」みたいに思っちゃうんですが、本当に感謝は尽きなくてですね。

主催のuzullaさん、会場を提供してくれたMicrosoftさん、スタッフさん、トークを聞きに来てくれた方々。そういう方々に感謝をお伝えしたいです。

それとお前好きすぎるだろと言う話なんですが、しんぺいさんにも感謝を伝えたいと思います。もしやこれはヤパチー行ってきたエントリーに偽装したラブレターかな?

とにかく!楽しかった!最高!また次(「の機会」という安全側に倒した表現はせず、是非やってほしい、手伝っていくぞ、というスタンス)も参加するぞ!

PlayStation 3が欲しかったのでその欲望を満たしてみたら想像以上に満たされた

PlayStation 3とか買ってもやりたいタイトルあるの?」と言っていたのは何年前か。多分PlayStation 3が発売された直後とかだと思います。今ではタイトルの本数もかなりの数に上るんじゃないでしょうか。だってPlayStation 4の時代ですもんね。

しかもBlu-ray Discも再生できるとか何だこれ最強だろって思います。

というわけでPlayStation 3を買いました。ついでにいろいろ揃えました。

ONKYO WAVIO アンプ内蔵スピーカー 15W+15W ブラック GX-D90(B)

ONKYO WAVIO アンプ内蔵スピーカー 15W+15W ブラック GX-D90(B)

【Amazon.co.jp限定】PLANEX ハイスピードHDMI Ver1.4ケーブル 1m (PS3/Xbox360) PL-HDMI01-EZ (FFP)

【Amazon.co.jp限定】PLANEX ハイスピードHDMI Ver1.4ケーブル 1m (PS3/Xbox360) PL-HDMI01-EZ (FFP)

PLANEX 光デジタル角型オーディオケーブル 1m PL-DA01

PLANEX 光デジタル角型オーディオケーブル 1m PL-DA01

Thunderbolt/HDMIケーブルは所有のMacBook Airと液晶ディスプレイを繋ぎたかったので、ついでに購入しました*1

で、届いたその日に各機器を繋いで、こんな感じに。

f:id:takkkun:20140114162641j:plain

スピーカーを置く専用の台が欲しいです。ぴったりなの探すより自作した方が早い気がする。まあそれは追い追い。

とりあえず早速念願であった大神 絶景版をプレイしてみました。いや、めっちゃ綺麗ですね。FF8で「うわー、すげー綺麗!」と感じた記憶が蘇えり、「これがHDMIの力か……」とかバカみたいなこと思いました(機器をHDMIで接続するのが初めてだった)。HDMIもあるでしょうが、昨今の技術やPlayStation 3の能力でしょうね。そして27インチにして良かったって思いました。今度はおおかみこどもの雨と雪や、ラブライブ!Blu-rayでも買いたいところです。

ひとしきり遊んだら今度はMacBook Airとディスプレイを繋いで、デュアルディスプレイを試してみました。とりあえずまだ観てなかったキルラキル13話を観て、正直PlayStation 3による体験より、こっちの体験の方が強烈なんじゃないかと思いました。ついでに買った割には予想外の充足を得られたThunderbolt/HDMIケーブル。

ちなみにオーディオは、PlayStation 3とスピーカーは光デジタルオーディオケーブルで繋いで直接出力し、ついでに液晶ディスプレイとスピーカーをアナログケーブルで繋いでMacBook Airからの音声は液晶ディスプレイ経由のスピーカー出力にしてみました。もう少し賢い方法があれば良いのだけど。

ひと通り堪能したので、あと何がいるかなと考えたところ、「あー、入力機器を切り替えるたびにHDMIのケーブルを抜き差しするの面倒」と思い至ったので:

iBUFFALO HDMI切替器HEAC対応2ポートBSAK202

iBUFFALO HDMI切替器HEAC対応2ポートBSAK202

も購入したのですが、これだけが充足を得られませんでした。

これのオスを液晶ディスプレイに差し、メス2口にPlayStation 3のとMacBook Airのを繋いで、切り替えを目論んだのですが、PlayStation 3の方は入力に問題ないものの、MacBook Airの方はノイズが乗り、見られたもんじゃない写り方になりました。このスイッチがThunderboltからの出力と相性が悪いのか、それともThunderbolt/HDMIケーブルの方がよろしくないのか、とりあえず購入した商品の組み合わせだと非常に都合が悪い感じです。「これなら写る!」って構成をご存じの方が居たら教えてほしいです。

とりあえず直に差す方向で乗り切りますが、基本的には非常に良い買い物をしたなあって思います。やっぱ新家電導入の体験はいつでもシビれるもんですね。

追記

接続が甘かったみたいで、先述のThunderbolt/HDMIケーブルとHDMI分配器でノイズが乗ることなく出力させることが出来ました。

*1:ちなみにHDMI(オスオス)ケーブルは液晶ディスプレイに付属してきたので、購入する必要ありませんでした

Capistrano 3への手引き

Capistrano、便利ですよね。

capistrano/capistrano

最近メジャーバージョンアップがあったのですが、使い方、というかスクリプトの書き方やお作法が変わり、「Capistrano 3にアップデートしたはいいけど全然動かなくてどうなってんだ」という流れはもはやお約束みたいです。

試しに僕も個人で作ってるウェブサイトのCapistranoをアップデートしてみたので、その上でこんなところに気を付けたいな、と思うポイントでも書いておきます。

capifyは使わない

Capistranoを使うときは$ bundle installをし、次に$ bundle exec capify .とするのがお約束の流れですが、これからはcapifyを使ってもcap installを使ってねと言われます。

ですので:

$ bundle exec cap install

としましょう。

マルチステージが最初から有効になっている

ひとつのスクリプトで複数の環境(ステージ)にデプロイするときは今までcapistrano/capistrano-extcapistrano/ext/multistageを使うのが常套手段でしたが、これはデフォルトで有効になっています*1

デフォルトで有効であるため、cap installを行うと、Capfileとconfig/deploy.rbという今まで通りのものに加え、config/deploy/staging.rbとconfig/deploy/production.rbが作られます。もしconfig/deploy/*に書き出されるステージ名を変えたい場合は:

$ bundle exec cap install STAGES=development,staging,production

とSTAGES環境変数でcap install渡してあげましょう。

Capistrano拡張の対応

Ruby on Rails然りですが、これだけバージョンが変われば、今まで使ってきた拡張も使えなくなるというものです。

ただいくつかの拡張に関してはcapistrano自身が対応させた拡張を公開していますので、そちらを使いましょう。

# Gemfile
source 'https://rubygems.org'

group :development do
  gem 'capistrano'
  gem 'capistrano-rbenv', github: 'capistrano/rbenv'
  gem 'capistrano-bundler', github: 'capistrano/bundler'
  gem 'capistrano-rails'
end

capistrano-railsは「Capistranoは別にRuby on Railsだけのものじゃないんだよ」を体現するために別扱いになりました。こちらはrubygems.orgにあるため、githubオプションなどでgitリポジトリを指定する必要はありません。

また、これらの拡張は今まで人によってconfig/deploy.rbなどにrequireを記述していたかもしれませんが、Capfileに書くのが通例のようです。実際、cap installによって生成されたCapfileには上記の拡張がコメントアウトされています。必要に応じてコメントアウトを外しておきましょう。

変数の参照

今まで:

set :application, 'example-app'

として設定した変数は直接applicationとローカル変数のように参照できていましたが、これからは出来ません。fetchメソッドを使いましょう。

set :deploy_to, "/u/apps/#{fetch(:application)}"

なお、current_pathなどは変数とはまた別の扱いらしく、今まで通りローカル変数のように参照できます。

変数名の変更

「なんで変えたんだ!」と言いたくなる気がしますが、repository変数はrepo_url変数と名前が変わりました。拡張ではありますが、capistrano/rbenvの場合、rbenv_pathではなくrbenv_type(:systemで/usr/local/rbenv、:userで~/.rbenv)で、rbenv_ruby_versionはrbenv_rubyになりました。

大きな変更というより、Capistrano開発者の好みの範囲での変更に思えてしまいますが、ドキュメント参照の上、適切に設定する必要がありそうです。

デプロイ先のサーバの設定

今までは下記のように書いていました。

role :app, 'app1.example.com', 'app2.example.com'
role :web, 'web1.example.com'
role :db, 'db1.example.com', primary: true

Capistrano 3でも上記記述でエラーが出るわけではないのですが、公開鍵での認証が出来なくなってしまいました。以下のように書くのがベターな気がします。

server 'app1.example.com', roles: %w(app), user: 'deploy', ssh_options: {
  keys: [File.expand_path('~/.ssh/...')]
}

正直ここら辺はよく調べていないのですが、サーバごとに認証を設定できたりする方が理に適っていて、個人的にはこちらの方が好きです。

共有ディレクトリの設定

Capistranoはdeploy_toの配下にcurrent, releases, sharedの3つのディレクトリを作成します*2

その中のsharedにはデプロイの度に上書きを避けたいファイルなどをディレクトリにまとめて置き、デプロイ時にそのディレクトリへシンボリックリンクを張るという処理が行なわれていました。そのシンボリックリンクを張るディレクトリはshared_childrenという変数で設定できていたのですが、それがlinked_dirsに変わりました。ちなみにlinked_filesという変数も追加されています。

ただ名前が変わっただけなら良いのですが、ちょっとディレクトリ構造が変わることがあるので、注意が必要です。具体的には:

set :shared_children, %w(public/system)

とすると、deploy:setup時にshared/systemが作成され、そのディレクトリがreleases/LATEST_RELEASE/public/systemとしてシンボリックリンクを作成するような動作になっていましたが、Capistrano 3では:

set :linked_dirs, %w(public/system)

とすると、shared/systemではなく、shared/public/systemが作成されるようになります。ですので、Capistrano 2から乗り換えた場合、サーバ側でちょっと手を加えてあげないと今まで参照できていたファイルが参照できない、などといったことが発生するため注意が要ります。

各タスクの記述

ここが一番大きな変更点だと思います。今までtaskメソッドを呼び出してタスクを定義し、そのメソッドへのオプションでどのロールで実行するのか、などを定義していました。タスクの中では愚直にrunを並べて、そのタスクが何を行うのかを記述していました。何度もrunを呼びたくないからと、コマンドを組み立て、runメソッド1回で全コマンドを実装するように心掛けていた人もいるんじゃないかと思います。

Capistrano 3では基本taskメソッドはタスク名しか渡さず、その中のonメソッドでいろいろ記述します。

task :example do
  on roles(:app), in: :sequence, wait: 5 do
    # ~
  end
end

そしてonメソッドに渡したブロックの中で実行するコマンドなどを記述していきます。

コマンドの記述

先述のように、onメソッドに渡したブロックの中にコマンドを記述するのですが、ここで様々なメソッドが使えるようになっています。

namespace :log do
  task :clear do
    on roles(:app), in: :parallel do

      # 渡したパスが存在するかを確かめ、存在しなければブロック内のコマンドを実行しない。
      # また、コマンド実行時に先頭に"cd 渡したパス && "を付与する
      within current_path.join('log') do

        # コマンド実行時に先頭に環境変数の設定を付与する。
        # 下記の例では"RAILS_ENV=rails_env変数の値"となる
        with rails_env: fetch(:rails_env) do

          # コマンドの実行成否を真偽値で取得する(元々あった?)
          if test '[ -f development.log ]'

            execute :rm, 'development.log'

           end

        end

      end

    end
  end
end

他にもasなどがあるのですが、これらはleehambley/sshkitというSSHによるコマンドの実行を構造的に記述するためgemの仕業です。タスクの記述方法然り、コマンドの記述方法然りなのですが、ここらへんの記述方法が変わったのはSSHKitを導入したからなのかなあ、と思います。

SSHKitを使えばexecuteをもっとスマートに書けたりするので、ぜひ活用していきたいところです。ちなみにonメソッドもSSHKitのものですね。

deploy:setup

なくなりました。いきなりdeployで大丈夫です。他にもいくつかタスクの名前が変わっていたりします。

他にもまだまだたくさんありそうですが、というかcap installで作成されたスケルトンから読み取れることしか書いてないのですが、とりあえずこんなところで。

*1:ていうか2.xのいつからかcapistrano-ext使わなくても有効でしたよね?

*2:Capistrano 3ではrepoも加わりました。単にcloneやpullをするための(サーバから見て)ローカルリポジトリです

EmacsからVimに乗り換えた

ちょっと前にEmacs + markdown-modeでドキュメントを書いていたのですが、Markdownって改行を入れると、ウェブブラウザ上では改行されていなくとも、HTML上で改行(単にCRが入ってる、という意味)されるじゃないですか? そういうときってウェブブラウザ上で見ると、どうしてもCRによるホワイトスペースが入っちゃうんですよね。それがもう嫌で嫌でしょうがないって人、結構いると思います。僕です。

て言うかそもそも改行を入れていたのは、1行あたり80文字程度に収めるためです。つまり、次行への送りを手動でやっていたわけです。そこからしてナンセンスなわけですが、じゃあなぜ行の折り返しを使わなかったのかと言うと、あれ、行の折り返しを使うとCtrl+nとかで移動したとき、折り返された部分を飛び越して、その次の行に飛んじゃいますよね。「あ〜〜〜〜〜〜!」って感じです。

それもEmacs 23からはvisual-line-modeというマイナーモードのおかげで解決できるのですが、恥ずかしながら僕が使っているのはMac OS X LeopardCarbon Emacsです。バージョンは22…… あまりに酷いので一昨日ぐらいに一番新しいMacBook Air注文しました。これで解決。

ですが、届くまで待てないので、なんか良い機会だし、Vimに乗り換えるか〜っていうのが経緯です。経緯長くてごめんね。

以下何をしたか、です。

続きを読む

MeCabのメモリ管理はどうなっているのか

以前mecab-rubyを用いた下記のコードがコケる場合がありました。

node = MeCab::Tagger.new.parse(text)

これはparseメソッドを呼び出している最中にMeCab::TaggerインスタンスがGCによって解放されてしまい、メモリ違反を起こすためです(昔のことなのであやふやですが)。なので、parseメソッドに与える文字列の長さが短い場合はエラーが発生せず、長ければ長いほど発生し易いといういやらしいものです。

まあ今手元のmecab-rubyMeCabのバージョンは0.996)で試したら'a' * 100_000な文字列を与えてもそんな問題は発生しませんでしたけどね…… おかしいな……

そもそもなんでこんな話を思い出したかと言うと、MeCabのメモリがどういう風に管理されているかが気になったからです。

MeCabは0.99になってからModelとLatticeという概念が導入され、マルチスレッド時における使用法が変わりました。Modelはこの問題によって存在を知りましたが、実を言うとLatticeは知りませんでした。で、さきほどLatticeが気になりだしたのですが、なぜLatticeはスレッド毎に生成しなければならないのか、あれ、メモリはどうなってるんだろう、と次々と疑問が湧いたのでMeCabソースコードで確認した次第です。

それのレポートがこちら。

なおMeCabのバージョンは最新の0.996です(ソースコード)。調べた対象のファイルはsrc/tagger.cppです。

続きを読む

Rubyにpackage_privateを導入しようとしたら

Rubyのメソッドの可視性はpublic, protected, privateの3種類です。そこに同一パッケージ(イコール同一gem?)からのみ呼べるpackage_privateを導入してみましょう。

結論から言うと断念しました。

以下のような場合は簡単です。

class String
  def nya
    "#{self}にゃ"
  end

  package_private :nya
end

privateなどでもお馴染の引数付きでpackage_privateを呼んだ場合です。これは指定されたメソッドを同一パッケージから呼ばれたか判断するメソッドで包んで、同一パッケージからの呼び出し(ここではcallerで判断しました)じゃない場合はNoMethodErrorを投げる、でおっけー。

ではなぜ断念したか。以下のようなケースだと雲行きが怪しくなってきます。

class String
  package_private

  def nya
    "#{self}にゃ"
  end

  private

  def pyon
    "#{self}ぴょん"
  end
end

この場合、privateが呼ばれたら、package_privateを解除したいわけです。ってことはprivateの定義も上書きしなくちゃいけない?

で、本格的に断念したのが以下のケース。

class String
  package_private
end

class String
  def nya
    "#{self}にゃ"
  end
end

この場合のnyaメソッドはデフォルトの可視性であるpublicで定義されるべきなのですが、package_privateを解除する術が思い付かず、断念って感じです。ここらへんからはCの世界なので、CRubyのコードも読んでみたのですが、組み込むのはなかなかしんどそう。うむー。

追記(2013-04-16 08:10)

と、こんな記事を書いておいたら、これを読んだ@kyubingから神に等しきコードが飛んできました。

最初パッと見たところ、2度目のclass Nekoでpackage_privateが有効になりっぱなしにならないかな〜と思いましたが、そこらへんがRubyの妙のようで。

Rubyインスタンスメソッドが追加されると、Module#method_added(method_name)がコールされます。そのときのバックトレースは以下のような感じになるみたいです。

ファイル名:メソッドが定義された行:in `<class:クラス名>'
ファイル名:クラスの定義が開始された行:in `<main>' # mainかはクラスを定義した場所に依る

Class.newやclass_evalを使うともう少し違うバックトレースになりますが、それでも基本的に「何行目でクラスの定義が開始されたか」という情報は取れます。

と言うことは、完全に断念したというケースでも判断が付きそうですね! すごい! @kyubing様々です。また改めてコードにしてみようと思います。

Rubyで任意のメソッドの呼び出しを記録するgemを作った

タイトルの通り。

takkkun/peeek · GitHub

インストールは:

$ gem install peeek

で。処理としては対象のメソッドを呼び出し記録用のメソッドで包んでいるだけです。使い方はREADME.mdなりをご覧ください。が、一部書き漏らしていることがあり、それをここに書いておきます。

peeekコマンド

一応コマンドが付属しており、コマンドラインからでも簡単に試すことが出来ます。

$ peeek -v
peeek-1.0.2
$ peeek -H'Kernel#puts' -e 'puts "Hello World"'
Hello World
Kernel#puts from main with "Hello World" returned nil in -e at 1

ちょっと試したいときに便利。かも。

何に使うんだーって言われると何かに使えるかもね、としか答えられない思い付きgemなんですが、Pull Requestなどがあればどしどしください。

httpstatusコマンドで、HTTPのステータスコード(

一般的なWeb Programmerならば、HTTP Status codeはすべて暗記していると聞きました。

しかし、僕は初心者なので、なかなか覚えきれていないので、HTTPのステータスコードをさがすのに便利なツールを用意しました。httpstatus.erlです。インストール方法は:

$ wget https://gist.github.com/takkkun/5002968/raw/569c3e3ea98d1cd8c17a9529c12ac8a88c97350a/httpstatus.erl
$ chmod +x httpstatus.erl

です。escript用に書いてあるのでコンパイルとか必要ありません。

$ ./httpstatus.erl 4
400: Bad Request
401: Unauthorized
402: Payment Required
403: Forbidden
404: Not Found
405: Method Not Allowed
406: Not Acceptable
407: Proxy Authentication Required
408: Request Timeout
409: Conflict
410: Gone
411: Length Required
412: Precondition Failed
413: Request Entity Too Large
414: Request-URI Too Large
415: Unsupported Media Type
416: Request Range Not Satisfiable
417: Expectation Failed
418: I'm a teapot
422: Unprocessable Entity
423: Locked
424: Failed Dependency
425: No code
426: Upgrade Required
428: Precondition Required
429: Too Many Requests
431: Request Header Fields Too Large
$ ./httpstatus.erl 40
400: Bad Request
401: Unauthorized
402: Payment Required
403: Forbidden
404: Not Found
405: Method Not Allowed
406: Not Acceptable
407: Proxy Authentication Required
408: Request Timeout
409: Conflict
$ ./httpstatus.erl 400
400: Bad Request
$ ./httpstatus.erl Not
304: Not Modified
404: Not Found
405: Method Not Allowed
406: Not Acceptable
416: Request Range Not Satisfiable
501: Not Implemented
505: HTTP Version Not Supported
510: Not Extended

以下コード。

#!/usr/bin/env escript
 
main([Query|_]) ->
    case matcher(Query) of
        {ok, Matcher} ->
            print(lists:filter(Matcher, http_statuses()));
        {error, {Message, Position}} ->
            io:format("Error: ~s at ~p character~n", [Message, Position])
    end;
 
main([]) ->
    print(http_statuses()).
 
matcher([C|_] = Query) when $0 =< C andalso C =< $9 ->
    case re:compile("^" ++ Query) of
        {ok, Regexp} ->
            {ok, fun({Code, _}) ->
                     re:run(Code, Regexp, [{capture, none}]) =:= match
                 end};
        {error, ErrorSpec} ->
            {error, ErrorSpec}
    end;
 
matcher(Query) ->
    {ok, fun({_, Message}) -> string:str(Message, Query) > 0 end}.
 
print(HttpStatuses) ->
    lists:foreach(fun({Code, Message}) ->
                      io:format("~s: ~s~n", [Code, Message])
                  end, HttpStatuses).
 
http_statuses() ->
    [
     {"100", "Continue"},
     {"101", "Switching Protocols"},
     {"102", "Processing"},
     {"200", "OK"},
     {"201", "Created"},
     {"202", "Accepted"},
     {"203", "Non-Authoritative Information"},
     {"204", "No Content"},
     {"205", "Reset Content"},
     {"206", "Partial Content"},
     {"207", "Multi-Status"},
     {"208", "Already Reported"},
     {"300", "Multiple Choices"},
     {"301", "Moved Permanently"},
     {"302", "Found"},
     {"303", "See Other"},
     {"304", "Not Modified"},
     {"305", "Use Proxy"},
     {"307", "Temporary Redirect"},
     {"400", "Bad Request"},
     {"401", "Unauthorized"},
     {"402", "Payment Required"},
     {"403", "Forbidden"},
     {"404", "Not Found"},
     {"405", "Method Not Allowed"},
     {"406", "Not Acceptable"},
     {"407", "Proxy Authentication Required"},
     {"408", "Request Timeout"},
     {"409", "Conflict"},
     {"410", "Gone"},
     {"411", "Length Required"},
     {"412", "Precondition Failed"},
     {"413", "Request Entity Too Large"},
     {"414", "Request-URI Too Large"},
     {"415", "Unsupported Media Type"},
     {"416", "Request Range Not Satisfiable"},
     {"417", "Expectation Failed"},
     {"418", "I'm a teapot"},
     {"422", "Unprocessable Entity"},
     {"423", "Locked"},
     {"424", "Failed Dependency"},
     {"425", "No code"},
     {"426", "Upgrade Required"},
     {"428", "Precondition Required"},
     {"429", "Too Many Requests"},
     {"431", "Request Header Fields Too Large"},
     {"449", "Retry with"},
     {"500", "Internal Server Error"},
     {"501", "Not Implemented"},
     {"502", "Bad Gateway"},
     {"503", "Service Unavailable"},
     {"504", "Gateway Timeout"},
     {"505", "HTTP Version Not Supported"},
     {"506", "Variant Also Negotiates"},
     {"507", "Insufficient Storage"},
     {"509", "Bandwidth Limit Exceeded"},
     {"510", "Not Extended"},
     {"511", "Network Authentication Required"}
    ].

最近のMeCabの使い方

MeCabC/C++で書かれた形態素解析を行うライブラリなのですが、ちょっと疑問に思うところがあって、以下のようなコードを書きました。

require 'MeCab'

mecab_options = '-Owakati'

1000.times do |n|
  begin
    tagger = MeCab::Tagger.new(mecab_options)
    # do something with tagger
  rescue => e
    raise "failed at #{n + 1} times, [#{e.class}] #{e.message}"
  end
end

マシンスペックに依りますが、大体例外が発生します。MeCab::Taggerインスタンスが破棄されているにも関わらず! 僕のMacBook Air(メモリ2GB)で試したところ、"failed at 70 times, [RuntimeError] "と、大体70回MeCab::Tagger.newを呼んだところで落ちます。例外の内容は不明。

プロセスどうなってんのと見てみると、メモリを13GBほど消費してました。ひどい。多分mallocに失敗とかそんなところでしょうか?

どうやらMeCab 0.99からマルチスレッドに対応したようで、本家のドキュメントのコード例も変わってました。要はマルチスレッドでMeCab::Taggerインスタンスをもりもり作る場合はMeCab::Modelのインスタンスを作り、そこからMeCab::Taggerインスタンスを生成しろよ、ってことみたいです。もしMeCab::Taggerインスタンスを直接生成すると、そのたびにMeCab::Modelのインスタンスが生成されるので、メモリを圧迫し、死亡、という流れのようですね。

ということでMeCab 0.99をインストールしている環境では以下のように書けば大丈夫みたいです。

require 'MeCab'

mecab_options = '-Owakati'
mecab_model = MeCab::Model.create(mecab_options)

1000.times do
  tagger = mecab_model.createTagger
  # do something with tagger
end

ちなみにMeCab::Taggerをひとつしか生成しないような環境であれば、MeCab::Taggerから直接インスタンスを生成しても構いません(コード例でもそう書いてある)。

ErlangでTwitterのUserStreamを受け取る

以前もErlangでTwitter Streaming APIを使うといったエントリを書いたのですが、いかんせん情報が古すぎます。UserStreamではなく、素のStreaming APIなのはともかく、認証がベーシック認証だったりします。

その割にはどうやら最近参照されているらしい。http://naoyat.hatenablog.jp/entry/2012/01/04/220639http://d.hatena.ne.jp/siritori/20120312/1331503357には以前のエントリのURLが貼られているようで。いや、なんかすみません。

ということで、ちゃんと動くかつOTPで書き直してみました。erlang-oauthに依存しています。

-module(userstream).
-author("Takahiro Kondo <heartery@gmail.com>").

-export([start/5, start/6, start_link/5, start_link/6, stop/1]).

-behavior(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-record(state, {id, processor}).

start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret) ->
    start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, []).

start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, Options) ->
    Args = [Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret],
    gen_server:start(?MODULE, Args, Options).

start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret) ->
    start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, []).

start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, Options) ->
    Args = [Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret],
    gen_server:start_link(?MODULE, Args, Options).

stop(Server) ->
    gen_server:cast(Server, stop).

%% callback functions

init([Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret]) ->
    Url = "https://userstream.twitter.com/2/user.json",
    Consumer = {ConsumerKey, ConsumerSecret, hmac_sha1},
    Options = [{sync, false}, {stream, self}],
    case oauth:post(Url, [], Consumer, AccessToken, AccessTokenSecret, Options) of
        {ok, Id}        -> {ok, #state{id = Id, processor = Processor}};
        {error, Reason} -> {stop, {http_error, Reason}}
    end.

handle_call(_, _, State) ->
    {noreply, State}.

handle_cast(stop, State) ->
    {stop, normal, State}.

handle_info({http, {Id, stream_start, Headers}}, #state{id = Id, processor = Processor} = State) ->
    send(Processor, {start, Headers}),
    {noreply, State};

handle_info({http, {Id, stream, <<"\r\n">>}}, #state{id = Id} = State) ->
    {noreply, State};

handle_info({http, {Id, stream, Part}}, #state{id = Id, processor = Processor} = State) ->
    send(Processor, {stream, Part}),
    {noreply, State};

handle_info({http, {Id, {error, Reason}}}, #state{id = Id, processor = Processor} = State) ->
    send(Processor, {error, Reason}),
    {stop, {http_error, Reason}, State}.

terminate(_, #state{id = Id, processor = Processor}) ->
    send(Processor, stop),
    httpc:cancel_request(Id).

code_change(_, State, _) ->
    {ok, State}.

%% private functions

send(To, Message) ->
    To ! Message.

ちゃんと動くかは確認しましたが、process_flagとかは呼んでいないのでそこらへんは適当に。gen_serverですので、ちゃんと設定すればそこまで手こずることなくsupervisor treeに組込めるかと思います。

本当はuserstreamモジュールをさらにビヘイビアにして、handle_status/2, handle_favorite/2とかで各イベントをハンドリングできるようにするといいんですが、それをやるとちょっと複雑になるので、そこまではやりません。

ちなみに使い方はこんな感じで。

-module(example).
-author("Takahiro Kondo <heartery@gmail.com>").

-export([start/0, stop/1]).

start() ->
    Processor = spawn(fun() -> process() end),
    ConsumerKey = "Your consumer key",
    ConsumerSecret = "Your consumer secret",
    AccessToken = "Your access token",
    AccessTokenSecret = "Your access token secret",
    userstream:start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret).

stop(Pid) ->
    userstream:stop(Pid).

process() ->
    receive
        {start, Headers} ->
            io:format("Start: ~p~n", [Headers]),
            process();
        {stream, Part} ->
            io:format("Stream: ~p~n", [Part]),
            process();
        {error, Reason} ->
            io:format("Error: ~p~n", [Reason]),
            process();
        stop ->
            io:format("Stop~n")
    end.
$ erl -s inets -s ssl
> {ok, Pid} = example:start().
ここにUserStreamからの応答が表示される(example:process/0で標準出力に吐き出してるため)
> example:stop(Pid).
Stop
>

OTPを使いつつ複雑すぎない書き方をしてみました。必ずしもOTPを使う必要はないですし、メリットばかりでもないんですが、アプリケーションがある程度複雑になってきたら使った方が良いかなと思います。それこそ書き捨てのコードでは不要でしょうが、あのプロセスが動いて、こっちであーでどーで、とかで頭のリソース割かれるならOTPを学ぶ価値はアリかなと。

余談ではありますが、余力があれば自作のtwitterモジュールをGitHubにでもあげておきたいもんですね。それにはTwitterREST APIはもちろん、先述したUserStream用ビヘイビアも書いてはあるんですよ。ただ随分前からメンテナンスをサボってるので、REST APIが古過ぎるという感じで…… なんか一から書いた方が早そう。