たごもりすメモ

コードとかその他の話とか。

Ruby誕生25周年記念イベントでしゃべった & After Partyやった

Ruby誕生から25周年を祝うイベントが開催され、そこの「Rubyの今」というセッションでデータ処理について話さないかという依頼があり、受けたので、話してきた。

Ruby25 | Top

本当にいろんな人が参加していて、みんなでわいわいとお祝いをする、という感じ。高橋会長やまつもとさんの話はあちこちのメディアに出ているから良いとして、Matzのご息女が出てきてMatzが壇上でものすごく動揺してたのがちょっと面白かった。いやあ、これこそがコンテンツだな。

他にスポンサー企業のブースがあったり、いろんなプロジェクト・コミュニティのポスター展示があったり。ポスター展示はよかったなー。あんなふうに自分のプロジェクトをアピールできるのはすごく良いと思う。見て回ったけどじつに楽しかった。

しゃべってきた

ということで、データ処理とは何を指すのか、どのようなプロダクトやサービスがあって、またその界隈を経由してどういうところでどのようにRubyが使われているかについてしゃべった。
自分が書いたコードについてではないスピーチ*1をするのはずいぶん久し振りで、また「お祝いをする」場だというのにあわせて内容もマインドセットも作ったものだから、15分弱という時間もあってだいぶ緊張した。が、まあまあうまくやれたと思う。他の人のプロダクトやプロジェクトを紹介するの、できるだけ紹介はしたいけど間違ったことを言うのは怖い、ということでけっこうキツい。今回はやれるだけのことはやった。

終わったあとで色々な人に良かったと言ってもらえたのはじつに嬉しいんだけど、ふたことめがだいたい「あんな話しかたもできたんですねえ」「酒が足りなかったのでは」などという感想で、複雑な気分というか普段のキャラクターが浸透しているというか、ううむ。

After Partyやった

Ruby25 After Party|IT勉強会ならTECH PLAY[テックプレイ]

気付いたときにはRuby25の大きいスポンサー枠は全部埋まってて、Treasure Dataとして何かできないかなーという検討の結果がこちらのイベント。180人定員のところ、最終的に170人弱は入っていたみたい。思ったよりだいぶ混雑した会場になっていたけど、やらないよりはだいぶ良かったかなという気がする。楽しんでもらえたかなあ。
技術評論社さんから最新号のWEB+DB PRESSを3冊いただいていたので、そこに著者陣のサインをいただいて会場プレゼント大会をやったりとかもしていた。わいわい。

ということで

じつにいいイベントでした。特に @_ko1 さんは本当にお疲れさまでした&ありがとうございました。
終わったあとも飲みにいってハシゴして、しかし疲れていたのか、最終的にはいっしょに飲んでいた人にちょっと迷惑をかけたかもしれぬ。君の名は。を途中まで観たところまでしか覚えてない……。おつかれさまでした。

*1:いや、幸運なことに自分が書いたコードのあるプロダクトも入ってはいるのだが

TD Tech Talk PLAZMAでBigdamについてしゃべった

今年はTreasure Dataの東京オフィスができて5年ということも兼ねてPLAZMAというイベントをやっているんだけど、その併催ということで TD Tech Talk をなんと2日間、初めて平日の午後の開催で行った。TD発のOSSが中心になるOSS Dayと、TDの内部実装についてあれこれ話すTD Internal Day。

techplay.jp
techplay.jp

あれこれ裏方もやっていたんだけど、多くの人に来ていただけたし、楽しんでもらえたような感じだった。またOSS DayとTD Internal Dayともに外部からの人にもしゃべっていただく時間を設けて*1みた。こっちも普段TDのエンジニアだけだと出てこない話がいろいろあって、本当によかったと思う。

しゃべった

ここ1年(!)やっているBigdamというプロジェクトがあるんだけど、そのプロジェクトの概要と全体の設計において重要なポイントをいくつか紹介する内容にしてみた。

これでも内容をだいぶ簡素化したんだけどそれでも30分に収まりそうにない分量になっちゃって、しょうがないからだいぶ早口でやりました。これは明らかによくないんだけど、うーん、他にどうにもならなかった。削れる場所がない。
分散システムをどういう目的で・何を達成するために設計するかという話はあまり聞いたことがない気がしたので、これはこれで新鮮な内容だったんじゃないかなという気はする。いっぽうでどうしても時間の制約から実装上のポイントとか他に検討した選択肢への言及(例えば、Kafkaを使わないの? などなど)が内容から漏れちゃうので、それは自分にとっても少し残念だった。懇親会ではけっこうそういう話をしていた。

なんにしろ疲れた、が、まあ、やっている話ができたのはよかった。

*1:これは実は昨年にあったFastly Yamagoya Meetupがすごくよかったので、これをパクらせてもらった

SENDAI IT COMMUNE #2にいってきた&しゃべってきた

こちらのイベントでしゃべらないかという依頼をいただきまして、いい機会だったのでお受けしていってきました。いやー、地方都市での登壇依頼、いつもたいへん嬉しいです。ありがとうございました。

techplay.jp

仙台市さんが仙台でITな感じを盛りあげよう、みたいな感じで行われているよう(雑な理解)で、東京でも既に一度あったようだしこの後も東京で行われるようですが、今回は仙台市内。ナイスな時に呼んでもらえました。

f:id:tagomoris:20180122132624j:plain

仙台、新幹線で早ければ東京から1時間半で着くという、とにかく圧倒的に近いのがすばらしいですね。あと酒と魚がうまい(重要)。個人的に楽しいおでかけでした。

しゃべってきた

データ分析基盤のことについて、ということだったんだけど他の方がサービス運営で使われるデータ分析の話をがっつりしてくれそうな感じだったので、自分はちょっと目先を変えてそういう基盤を作るときに必要な技術について話してた。ということはつまり、リトライと冪等性についてである。

特にTreasure Dataの主要な顧客層であるところの非IT専業企業の人が使うデータ分析基盤にはどういう特徴が必要で、それを満たすためには何について考えられていないといけないかを話したつもり。
結果としてものすごく地味な技術の話になったけど、会場で熱心に聞いてくれる人も多く、後から感想を聞くに身につまされて聞いていたという人もいたので、少なくとも全くポカーンという感じではなかったようだった、ので、それだけでも良かった。自分としても久々にこういうことをちゃんと整理して考えるいい機会になった。

聞いてきた

全体としてふたつのトピックがあって、前半がデータ分析基盤、後半はクラウドという感じ。
内容としては都内にいてもよく聞く……けど、楽天さんの話は面白かったな、具体的に発生した事件とその対処としてのデータ分析の利用の話で、やっぱり具体性のあるユースケースは常に参考になるし、面白い。しかしアホみたいにでかいHiveQLを書くよりはMapReduce書いたほうがいいと思う。w

会場が仙台市内の繁華街にあるライブハウスで、普段しゃべるよりもだいぶ高い位置にステージがあるのがちょっと面白い感じだった。面白会場での勉強会、良いと思います。

いってきた

仙台なー、最高だよな、酒も魚がうまい(重要なことなのでもう一度)。せっかくなので前日入り、翌日帰宅ということで2泊していろいろ食べてきた。

f:id:tagomoris:20180122133225j:plain
f:id:tagomoris:20180122133557j:plain

なにしろ5月末にはRubyKaigiが仙台でもあるということで、その下見も兼ねt

f:id:tagomoris:20180122134232j:plain

仕事! お仕事の一環で行ったんですよ!!!!!!

よいイベントでした。

mrbgemをビルドしてテストを手元で走らせる(2017年版)

mrubyを使っていたところ、あるmrbgemが思った通りに動かなかったとする。あなたはまず真っ先に自分のコードを疑い、次にmrbgemの挙動を疑うであろう。
しかしmrbgemにはテストが十分に書かれていないかもしれない。ので、自分でテストケースを追加して手元で走らせ、様子を見たいと思うのが人として自然な成り行きである。

ところでmrbgemのリポジトリなどを見てもどのようにビルドしテストを実行させているのかが全く不明なケースが多く、なにこれどうやるの? という疑問を当然のように持つと思うので、そのような人のためのメモがこのエントリ。

ちょっと調べると以下のようなエントリがひっかかりますが、これは2年前のもので、この通りに build_config.rb を書いて上書きして走らせてもmrbgemのテストは実行されません。注意。
mrubyのモジュールmrbgemを開発した際に簡単にTravis CIを設定する方法 - 人間とウェブの未来

どうするか

現代(2017年10月)においては mrbgem のリポジトリのクローンからテスト実行までは以下のようにします。例として mruby-foo というmrbgemをどこかからcloneしてきて、その中のテスト(おそらく test/*.rb に書いてある)を実行するものとします。

手順としてはまず対象のmrbgemのcloneから。

$ git clone https://github.com/foobar/mruby-foo.git
$ cd mruby-foo

その中でさらにmrubyをcloneする(テスト実行用のmrubyバイナリをここから作る)。

$ git clone https://github.com/mruby/mruby.git
$ cd mruby

そのmrubyのビルド設定において、テスト実行時に有効になる設定があるのでそこに1行を加える。
この "conf.gem '..'" によりmrubyの親ディレクトリ(つまりmruby-foo)が依存関係に含まれ、かつテストも実行されるようになる。

@@ -111,20 +111,21 @@ end
 
 MRuby::Build.new('test') do |conf|
   # Gets set by the VS command prompts.
   if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
     toolchain :visualcpp
   else
     toolchain :gcc
   end
 
   enable_debug
+  conf.gem '..'
   conf.enable_bintest
   conf.enable_test
 
   conf.gembox 'default'
 end

あとは rake test すればよい。末尾にテスト実行結果が出てくるはず。おしまい。

>>> Test test <<<
mrbtest - Embeddable Ruby Test

...................................................................................................?.........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................?..........................................................................................................................................................................................
Skip: Struct.new removes existing constant  redefining Struct with same name cause warnings
Skip: Module#prepend super in alias  super does not currently work in aliased methods
Total: 982
   OK: 982
   KO: 0
Crash: 0
 Time: 0.58 seconds

................
Total: 16
   OK: 16
   KO: 0
Crash: 0
 Time: 0.56 seconds

余談

ところでこの mruby のテストで使える assert がわからん。以下のように書いても OK と言われてしまう。どういうこと?

assert('Must fail') do
  1 == 0
end

追記(assertについて)

上述の余談の件、わかった。最近に mruby/test/assert.rb における assert の挙動が変更されていた。
github.com

ということで上記のような(falseを返したらfailすると期待しているような)テストケースは全部動かなくなっている。以下のように明示的に assert_* メソッドを書くべき。

assert('Must fail') do
  assert_equal 1, 0
end

これ、今までいくつかのmrbgemを見た記憶によると(特に大量にmrbgemを書いている何人かがauthorの)mrbgemのテストが軒並みぶっこわれてるんじゃないなあ。きびしい。

ISUCON7予選で敗退した

あー、負けたー。「Asakusaの方から来ました」というチーム名で、Asakusa.rbでよくいっしょする @joker1007 さん、および @yancya さんと出た。最終結果は111400くらい? ただし20時*1を過ぎても4〜6万くらいをうろうろしてて、最終的には20:10頃に入れた変更でスコアが倍になり、なんだこれー? と言ってる間にタイムアップした。
去年のISUCON6決勝はやるべきことをやれなくて負けたので素直に悔しかったけど、今年はなんか問題設定の意図がまったくわからなくてそのまま不完全燃焼で手が停まって4時間経過で死んだという感じなので、なんだかなー、という気分。あんま疲れないまま終わってしまった。

たぶん出題者にはお礼の言葉が今週のblogエントリ等では並ぶと思うので、負けた人間としてはここで一発だけdisっておきたい。結果論として、問題設定としてはISUCON4決勝の問題で Cache-Control をみんなが有効にした後の状況を作りたかったんだろうなー、という気がする。けど、ハイコンテキストすぎるだろ。あと Cache-Control: public を明示的に有効にしないとキャッシュしてくれないっていうのは明らかに特定のCDNを意識していると思うんだけど、参加者に複数ホストを最初から与えておいて、最終的にやらせたかったのは外部サービスにいかにキャッシュさせるか*2ってことですか?

いやまあ、そういう問題だったし、そこをクリアした上で勝負を争えという問題だったんでしょうね。そういう問題だと気付いた時点で残り1時間もなくて、潜在的に抱えこんでいた問題に対処しきれず負けたのは、もう普段Webサービスから遠ざかった自分の実力なんだろうと思います。
最終的にはこれCDNかということに気付いたけど、そのときに周囲のチームのようにはスコアが上がりきらなかったのは、完全に自分のオペミスのせいだった。これだけは本当に言い訳ができないミスで、チームメイトに申し訳なかったなと思う。あの1手で本選出場を逃したと言っても過言ではない(詳細は後述)。

予選環境は快適でした*3ベンチマークもエンキューした直後には始まる状態がずっと続いていて、1チームに複数サーバを与えるということも含めて、さくらインターネットさんの担当側は大変だっただろうなと思います。お疲れさまでした&ありがとうございました。
環境構築であれこれあったためかスケジュール上もあれこれあって、運営の941さんも物凄く大変だっただろうなと思います。お疲れさまでした。いつもありがとうございます。

経過

joker1007さんのエントリに細かい経過がある。

https://cdn-ak.f.st-hatena.com/images/fotolife/j/joker1007/20171023/20171023022330.jpg

SSHログイン含めてちょっと最初の環境整備でバタついて、結局この図を描いたときにはスタートから1時間半くらい経ってたと思う。負荷とトラフィックをうまく3台にバラす、ということをいつもの決勝の通りメインで考えていた。WebDAVと言ったときはまたWebDAVかと自分でも思ったけど、あとで考えたらS3的なオブジェクトストレージが欲しいときに簡単に実現するための自分の発想がWebDAVなんだなと。

メッセージまわりのコードの変更はあとの2人にお願いして、自分は画像をうまく配信するためにhttp_dav_moduleを有効にしたOpenRestyのビルドと設定、あとは手元でもちゃんと動くようにRubyのアプリケーションでもPUTを受け付けてローカルに書き出すリクエストハンドラを追加したりしてた*4

で、多少ミスったりしつつ*5もしつつ、3ノード全部で画像をnginxから直接返せるようになったのがこのcommit、17:25。けっこう時間かけちゃってるなあ。

fix to upload icons to servers · tagomoris/isucon7-elimination@b380245 · GitHub

で、ネットワークが詰まって、なんだこりゃーとなった。今にして思えば画像をDBから出して直接nginxから返すのだけを初手にやって、それを複数台構成に直す、としていればたぶん今回のミスは踏まずにすんで、普通に戦えてたんだろうなという気はする。いきなり最初からベストな最終形を描いてそこを目指して進むのは自分の仕事の進めかたのよくない所だろうかという気はちょっとする。……んだけど、うーん、普段はそっちの方が効率いいんだよなあ。

この作業のとき、以下のような手順を踏んだ。これが後にして思えば決定的なミスだった。

  1. スクリプトを書いてDBからファイルシステムに画像を書き出す (OK)
  2. 書き出した画像をgitリポジトリに突っ込む (どちらかと言えばNG、ただし決定的ではない)
  3. サーバ3台でそれぞれ git pull で画像をfetch (OK)
  4. WebDAV用の領域に画像を cp -r で移す、というコマンドを3台でそれぞれ叩く (完全NG)

最後の手順により3台のサーバでそれぞれ初期の画像セット(67ファイル)のタイムスタンプがズレて、最終的に Cache-Control: public がついた状況でCDN(と想定されるクライアント)側にうまくキャッシュが保持されなかった。
これ、単に tar.gz で画像ファイルを固めて各3台のサーバのWebDAV領域でそれぞれ展開していれば全く問題なく処理されてただろうし、あるいは cp するときに -a つけていればそれでもよかったはずで、今となってはそのあたりの手順を当然のようにとってても不思議ではなかったと思うんだけど、なんか、そうしてしまった。本当に悔やまれる。

その後、ネットワークが詰まった状態になったのはすぐに分かったので Cache-Control のことはすぐに思い付き nginx で expires の指定を入れるものの、まったく状況が改善せず、なんで? と不可解な状況に陥った。
tcpdumpをとって調べたんだけど、Last-Modified の入ったレスポンスを返していれば普通のWebブラウザ等のクライアントであれば当然入ってるはずの If-Modified-Since の指定が入っておらず、でも周囲のスコアを見てみると画像のバイナリを返していれば不可能なスコアを達成しているのは明らかで、どういうことじゃこれは、と悩み続けてた。

特に悩ましかったのがこれ*6で、同じ静的ファイルなのに違う傾向のリクエストを送ってくるCDNがあるなんて想定してなかったから、完全に思考が空転してた。ずっと「このクライアントはなんなの?」って言い続けてた気がする。

ずーっと煮詰まってたんだけど、20:10のこのcommit、半笑いで「まっさかねー」と言いつつ Cache-Control: public を入れた。ベンチ回してトイレいって、戻ってみたらスコアが倍増しててうぎゃあコイツCDNだ! とか言ったような気がする。

add Cache-Control: public explicitly · tagomoris/isucon7-elimination@079a30a · GitHub

ここで11万点まで上がったんだけど、しかしそれでもネットワーク帯域は使い切った状態で、なんで? と言い続けてた。
これは自分のミスでハマったところ。最初に撒いたファイルの Last-Modified および ETag が各サーバで異なってたため、CDNを想定したクライアントのエッジキャッシュヒット率が 1/3 に落ちていたんだと思う。んでそのたびに画像をダウンロードしに来ていたから初期データへのアクセスでネットワークをまず使い潰した。これでベンチマーカーの負荷も上がらず、結局初期データへのアクセスだけが続くようになってドツボにはまったっぽい。

その後にアップロードされてくるデータをサーバ3台に渡すのは、まあちゃんと気を使ってなかったとはいえ、1秒をまたぐこともほとんどなかったはずなので、概ね大丈夫だったはずと思っている。もちろんもっとちゃんとやりようはあったけど。
とはいえ「このクライアントなんなの?」という疑念が強過ぎて、Last-ModifiedおよびETagをちゃんと返せているかというところまで頭が回らなかった。CDNがエッジキャッシュを作るときには当然そこを厳密に見ているはず、という発想もなかったので、これは、うーん……もうちょい時間があればなあという気もするけど。うう。

他の2人がちゃんとメッセージ回りのロジック改善をやってくれていたので、あとは /icons の初期データさえどうにかなっていたらだいぶ上位のスコアが出てたんじゃないかなあという気がする。心残りは大きい。

結論

頑張って仕事しよう、と思ったものの、正直こっちの方向にISUCONが行くといくら自分の仕事してても弱くなる一方なので、引退かなあ、とか考えつつある。
去年の予選は*7はてなのサービスだ! というのがわかったので面白がれたところはあったけど、今年のはもう、なんなの、ちょっと無理、みたいな気分。

*1:今回は13時〜21時というスケジュール

*2:しかもそんなものが存在するという前提は教えない

*3:……fail2banを除いて。メンバーのひとりでもちょっとログイン試行に失敗を繰り返すと全員がbanされてたので、最初にちゃんと全員のvalidな公開鍵を入れ終わるまでちょっとトラブってたのはきつかった

*4:ISUCON3の決勝のときにもほぼ似たような構成だったけど、このときは何も考えずにWebDAVが有効なnginxが無いと動作確認もできない状態にしてしまって難儀した覚えがあったので、その教訓から

*5:OpenRestyビルド時に --with-ipv6 つけ忘れてアーッってなったり。IPv6がちゃんと最初から有効なの、さくらインターネットさすがだな! って感心しながらビルドしなおした。

*6:あとでfujiwaraさんもなんかtweetしてたので確認ミスではなかったようで、ちょっと安心している

*7:競プロ勢向きだなー! と思いはしたものの

Javaのリリースサイクル変更により Oracle JDK が一般ユーザに提供されなくなるのではという話があったけど誤読ではないかという話

雑に書くぞ!

Faster and Easier Use and Redistribution of Java SE | Oracle Java Platform Group, Product Management Blog

このエントリを読む限り、Oracle JDKのダウンロードが商用サポート契約者にのみ限られる、という話は書いていない。

The Oracle JDK will continue as a commercial long term support offering
* The Oracle JDK will primarily be for commercial and support customers once OpenJDK binaries are interchangeable with the Oracle JDK (target late 2018)
* Oracle will continue to enhance the packaging and distributing of complete ready-to-run applications

"The Oracle JDK will *primarily* be for commercial and support customers ..." とあるけど、わざわざ primarily とついてるってことは secondarily があるってことで、それは通常の(今までどおり)テキトーにダウンロードしてたユーザのことじゃないの?

the Oracle JDK will remain as a long term support (LTS) offering for Oracle’s commercial and support customers.

Oracle JDK はLTSの提供のために残る、とあるが、サポートサービス提供の対象が Oracle JDK であるというだけで Oracle JDK の利用はOracle顧客に限られる、とは書いていない。

Commercial Features packaged separately from the Oracle JDK, such as the Advanced Management Console, will continue to be provided separately through Oracle’s “Java SE Advanced” commercial offering.

商用専用の機能は Oracle JDK とは別にパッケージされ、"Java SE Advanced" の顧客に、Oracle JDKとは別に("separately")、提供される。これはわざわざバイナリを別パッケージでクローズドチャネル経由で提供すると言ってる。Oracle JDK自体には言及していない。

Oracle Java SE サポート・ロードマップ

たこちらの日本語の公式情報を読んでも、Oracle JDKは商用チャネルのみでのダウンロード、とは書いていない、ように読める。

以下で説明される Oracle JDK サポート・ロードマップの要点を述べるとすれば、2018 年 9 月を過ぎると商用利用目的の Java SE 8 の更新版は公式ダウンロード・サイトに掲載されなくなることです。Java SE 8 及びそれ以前のバージョンに対する重大なバグ修正、セキュリティ修正及び一般的なメンテナンスへの継続的なアクセスが必要なお客様は、Oracle Java SE Advanced, Oracle Java SE Advanced Desktop または Oracle Java SE Suite によって長期サポートを入手することができます。その他のユーザは、Oracle JDK または OpenJDK の最新のメジャーバージョンにアップグレードすることを推奨しています。

Java SE 8への継続サポート(が適用されたバイナリ)が必要な人はサポート契約が必要、それ以外の人は "Oracle JDK または OpenJDK の最新メジャーバージョンにアップグレードすることを推奨" している。ということは普通に Java 9 の Oracle JDK はダウンロード可能なんじゃないの? ということ。
Java SE 8 への長期サポートが必要なら契約してねっていうのはまあ普通ですよね。(嫌な人は古いバイナリを使い続けるなりOpenJDKを使うなりすればよい……よね?)

ということで

まあ実施されてみないと実際のところわからない*1けど、たぶん、騒ぐようなことじゃないと思いますよ。

追記

えー。うーん。まあでも、わかんないな。

*1:あるいはOracleの人がなんか追加で言うかも

Rubyで try-with-resources (あるいは with/using)をやりたいという話

前のエントリで書いたうちに入っていたが、Rubyでも try-with-resources (あるいはPythonで言う with やC#で言う using)が使いたいなー、という話。

Feature #13923: Idiom to release resources safely, with less indentations - Ruby trunk - Ruby Issue Tracking System

Ruby本体にFeature request出してみたんだけど、ライブラリでやればいいんじゃないの、という結論になってしまって、ええーと思っている。ライブラリだといくらでもある方法のひとつになってしまって、他のプログラマ(他所のライブラリ作者とか)に「これを使え」という話がしづらいんだよねえ。あとgemにして広く使われたとしても、この機能のバージョン指定がgemどうしで衝突してトラブルとか、死んでも死にきれない感があるし。
どうせやりかたの選択肢がほとんどないんだから、こういうものは言語側で方法を提供してほしい、と思ってはいる。

どういう用途でこれが必要なのかというと複数のリソースを確保しつつ確実に(逆順で)解放してほしいというケースで、最近思い付いた良い例としては (1) データベースへの接続を作る (2) その接続を用いて PreparedStatement を作る、みたいなものが当たる。

begin
  conn = Database.new(addr, port, dbname)
  begin
    stmt = conn.prepare(sql)
    # stmt.execute(...)
  ensure
    stmt.close
  end
ensure
  conn.close
end

Rubyであればopenとブロックを使ってこのリソース確保と解放をする例が多いと思うが、これはリソースひとつにつきインデントがひとつ深くなるので簡単にネストの深いコードになり、全体として簡潔とは言いがたい見た目になる。

Database.new(addr, port, dbname) do |conn|
  conn.prepare(sql) do |stmt|
    # stmt.execute(...)
  end
end

たこういったブロックを渡せるメソッドがそれぞれのクラスで実装されている必要がある、が、現実としてそうなっていないケースは多い。例えば上記のコード例では conn.prepare にブロックを渡せる必要があるが、mysql2 の prepared statement を作るためのメソッドには実際にはブロック渡しはできない。

ところでライブラリとして実装してみる

とはいえ、とりあえずライブラリとしてでも実装がないと話にならない、みたいな面もあるのかなと思ったので、自分でエイヤと実装してみた。bugsのやりとりではdeferでやるという方向に倒れかけてたけど、自分の好みじゃない*1ので、実装するなら自分が書きたいように書けるようにしてみた。

github.com

これで上記の例は以下のように書ける。

require "with_resources/toplevel"
using WithResources::TopLevel # once per file

with(->(){ conn = Database.new(addr, port, dbname); stmt = conn.prepare(sql) }) do |conn, stmt|
  # stmt.execute(...)
end

簡潔! 何よりネストが深くならない!

見た目としてはJavaのtry-with-resourcesに似ていて、動作もそっくりそのまま……のはず。リソース解放時などに複数の例外が起きたときはふたつめ以降の例外は `e.suppressed` で参照できる配列*2に格納されるようになっているのも try-with-resources のまま。

キモとしては with に渡した lambda に複数の文が書け、そこのローカル変数にセットした値が後のブロックにも渡ってくるというところ。これはキモい。
TracePointとbindingを使ってあれこれやった。ちょいマジカルで、かつ実装としてはちょっと不安定だと思う。リソース解放用のブロックに存在するローカル変数だけをキャプチャしたいんだけど、TracePointを使ってあるブロック内に存在する記述だけを正確に追いかけることが現状不可能なことによる。変な書き方したら動作がおかしくなるかも。

ということで

作りました。こんなんどうですかね、という提案に近いですが。

*1:golangのdeferはその関数の終わりで自動的に解放されてインデントも増えないのに対して、Rubyで自分で実装する方向だとブロックをひとつ作らないといけなくてgolangみたいに簡潔な記述にならないのが気にくわない

*2:extendで動的に足している、まじべんり