EmacsからVimに乗り換えた
ちょっと前にEmacs + markdown-modeでドキュメントを書いていたのですが、Markdownって改行を入れると、ウェブブラウザ上では改行されていなくとも、HTML上で改行(単にCRが入ってる、という意味)されるじゃないですか? そういうときってウェブブラウザ上で見ると、どうしてもCRによるホワイトスペースが入っちゃうんですよね。それがもう嫌で嫌でしょうがないって人、結構いると思います。僕です。
て言うかそもそも改行を入れていたのは、1行あたり80文字程度に収めるためです。つまり、次行への送りを手動でやっていたわけです。そこからしてナンセンスなわけですが、じゃあなぜ行の折り返しを使わなかったのかと言うと、あれ、行の折り返しを使うとCtrl+nとかで移動したとき、折り返された部分を飛び越して、その次の行に飛んじゃいますよね。「あ〜〜〜〜〜〜!」って感じです。
それもEmacs 23からはvisual-line-modeというマイナーモードのおかげで解決できるのですが、恥ずかしながら僕が使っているのはMac OS X LeopardでCarbon Emacsです。バージョンは22…… あまりに酷いので一昨日ぐらいに一番新しいMacBook Air注文しました。これで解決。
ですが、届くまで待てないので、なんか良い機会だし、Vimに乗り換えるか〜っていうのが経緯です。経緯長くてごめんね。
以下何をしたか、です。
続きを読むMeCabのメモリ管理はどうなっているのか
以前mecab-rubyを用いた下記のコードがコケる場合がありました。
node = MeCab::Tagger.new.parse(text)
これはparseメソッドを呼び出している最中にMeCab::TaggerのインスタンスがGCによって解放されてしまい、メモリ違反を起こすためです(昔のことなのであやふやですが)。なので、parseメソッドに与える文字列の長さが短い場合はエラーが発生せず、長ければ長いほど発生し易いといういやらしいものです。
まあ今手元のmecab-ruby(MeCabのバージョンは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を作った
タイトルの通り。
インストールは:
$ 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の使い方
MeCabはC/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/220639やhttp://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にでもあげておきたいもんですね。それにはTwitterのREST APIはもちろん、先述したUserStream用ビヘイビアも書いてはあるんですよ。ただ随分前からメンテナンスをサボってるので、REST APIが古過ぎるという感じで…… なんか一から書いた方が早そう。
autotest-twitterでブヒる
最近とあるgemを書きながら付随するgemを書いてて優先すべきそれが中々進まない昨今ですこんばんは。
で、恥ずかしながらテストファーストってあんまりやったことなくて、今それを実践しながらの開発をしています。使っているツールはRSpecなんですが、コマンドひとつでテストできるとは言っても、今度はそのコマンドを実行するのがめんどくさい。ひたすら怠惰な生き物ですね。
そういう生き物たちにうってつけなのが当然あって、それのひとつにautotestってのがあります。しばらくはautotest + autotest-growlで開発してたんですが、家で使ってるマシンはMac、職場で使ってるマシンはUbuntuなんですね。Macには当然Growlをインストールしてるんですが、UbuntuとなるとGrowl以前の問題です。なので「Twitterにテストの結果をツイートして、あとは各OS向けのTwitterクライアントから通知すればいいんじゃね」と思い至りました。というわけでautotest-twitterです。まあ後からよく調べたらautotest-growlはLinuxにも対応してましたけどね。ちくしょう。
使い方
README読めと言いたいところですが、GitHubに置いてあるのがいい加減なのでアレです。何がアレってテストを書いていないところですよね。まあとりあえずテストの対象となるアプリケーションなりライブラリが置いてあるディレクトリに.rspecを作り:
--format nested --color
を、.autotestに:
require 'autotest-twitter' Autotest::Twitter.configure do |config| # ツイートするアカウントのアクセストークンを設定 config.consumer_key = 'your consumer key' config.consumer_secret = 'your consumer secret' config.oauth_token = 'your access token' config.oauth_token_secret = 'your access token secret' # ラベル。アプリケーションの名前とか config.label = 'any application' # テストの結果に応じてアイコンを変えられるので、そのアイコンが # 置いてあるディレクトリ # - missing.png: テスト自体がない場合のアイコン # - failed.png: テストに失敗した場合のアイコン # - pending.png: ペンディングが存在する場合のアイコン # - passed.png: テストに成功した場合のアイコン config.image_dir = 'path/to/icons' # テストの結果に応じたツイートの内容。$で始まるのは変数 # - $label: config.labelで設定した内容 # - $all: テストの全件数 # - $failed: 失敗したテストの件数 # - $pending: ペンディングしてるテストの件数 config.missing_messages = ['$labelのテストが存在しないよ'] config.failed_messages = ['$labelのテストに失敗したよ。$all件中$failed件がダメみたい'] config.pending_messages = ['$labelのテストに$pending件のペンディングがあるよ'] config.passed_messages = ['$labelのテストに成功したよ! $all件あったみたいだね'] end
を、Gemfileに:
source 'https://rubygems.org' gem 'autotest' gem 'autotest-fsevent' gem 'autotest-twitter', :git => 'git://github.com/takkkun/autotest-twitter.git'
こう。で:
$ bundle --path vendor/bundle
でもしてautotest諸々をインストール。後は:
$ bundle exec autotest
でテストを開始。後はファイルに変更があるたびにテストが走り、結果に応じてツイートされるはずです。config.image_dirを設定してればアイコンも変わります。ちなみにRSpecでしか試していませんし、とりあえず動いてるっぽいってことしか確認してないのであしからず。
ちなみに僕は @Shinobu_DD で試していました。まるでアイコンセットのような画像(TVアニメ偽物語の一部でしょうが)があったので。でもまあ「$labelで$pending件ペンディングがあるようじゃな。お前様の生き様が垣間見えるの。かか」とか打ってると頭抱えたくなりますし、いざブヒろうにも全然テンション上がらないのであんま向いてなかったようです。ていうか元々そういうのじゃないし!
まあでもブヒろうと思えばブヒれるので、テストがコケたらツンツンされたり、テストが通ったらデレデレされたりして、「今日も開発がんばりましゅううう」とか言ってればいいんじゃないですかね。
あとさっき思い付いたんですけど、ツイートするアカウントを自分のアカウントにし、passed.pngをいつも使ってるアイコン、failed.pngをとてつもなく恥ずかしいアイコンにすると面白いんじゃないかと思います。はやくテスト通さないとエラい思いをするハメになるというマゾい開発が出来ていいんじゃないかナーーー。
Exporterでエクスポートされる関数の挙動を変更する
ひょんなところにこんなモジュールがあRuby。
package Hoge; use strict; use warnings; use base qw/Exporter/; our @EXPORT = qw/hoge/; sub hoge { print "Hoge::hoge called\n"; } 1;
このHogeモジュールのhoge関数を呼び出すためにこんなコードを書Chrome。
use strict; use warnings; use Hoge; hoge; # Hoge::hoge called
いろんな事情が絡んで、Hoge::hoge関数の挙動を変更したEthernet。
use strict; use warnings; use Hoge; { no warnings 'redefine'; local *Hoge::hoge = sub { print "anon called" }; hoge; } hoge; # Hoge::hoge called
一度目のhogeでanon calledと出力したいのだけど、これはHoge::hoge calledと出力されちゃWindows*1。
Exporterを使うと、エクスポート対象となるパッケージ(main)に指定された関数がコピーされているので、元の関数(Hoge::hoge)を書き換えてももはや手遅Rails。
でもどの名前空間にあるhogeが呼ばれるか分からない時もある訳Debian。コンパイル時に名前を解決するためにエクスポートしているけど、実際呼び出す時は必ず書き換えるようにしたりする訳Delphi。
ということでこうすれば良いんじゃないNode.js。
package Hoge; use strict; use warnings; use base qw/Exporter/; our @EXPORT = qw/hoge/; sub hoge { _hoge(@_) } sub _hoge { print "Hoge::hoge called\n"; } 1;
use strict; use warnings; use Hoge; { no warnings 'redefine'; local *Hoge::_hoge = sub { print "anon called" }; hoge; # anon called } hoge; # Hoge::hoge called
間接的に呼べば問題ないようDarwin。関数呼び出しのオーバヘッド増えるけど見ないふLisp。
おっPython。
*1:悔しい
単数形/複数形の変換ルールを独自に定義する
必要に迫られたので探してました. 案の定用意されたメソッドで好き勝手できるようになってました.
Rails御用達のActiveSupportの場合.
require 'active_support' require 'active_support/inflector' # Railsは自動で取り込んでくれるだろうけど, ActiveSupportを単体で使う場合は取り込んでくれないみたいです ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'octopus', 'octopi' end ActiveSupport::Inflector.inflections.irregular 'octopus', 'octopi' # コレでも一緒
イレギュラーなケース以外にもいろいろと定義できるので, ActiveSupport::Inflector::Inflectionsのドキュメントなり読みましょう.
Sequelもモデル名とテーブル名の変換に同様の仕組みを用いてる.
require 'sequel' Sequel.inflections do |inflect| inflect.irregular 'octopus', 'octopi' end