Rubyで try-with-resources (あるいは with/using)をやりたいという話
前のエントリで書いたうちに入っていたが、Rubyでも try-with-resources (あるいはPythonで言う with やC#で言う using)が使いたいなー、という話。
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ので、実装するなら自分が書きたいように書けるようにしてみた。
これで上記の例は以下のように書ける。
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を使ってあるブロック内に存在する記述だけを正確に追いかけることが現状不可能なことによる。変な書き方したら動作がおかしくなるかも。
ということで
作りました。こんなんどうですかね、という提案に近いですが。
RubyKaigi 2017で広島に行ってきた&しゃべってきた
いやー、よかったですね。旅行としても楽しかったし、いいカンファレンスでしたね。
自分が出したTalk proposalも通ったことだし、わいわいと広島まで行ってきた。非常によくオーガナイズされて会場では快適に面白い話ばかりえんえん聞けるし、そのへんにいる人達といろんな議論をしていろいろ良いことがあった。すばらしいカンファレンスでした。主催者ならびに運営協力のかたがた、ありがとうございました。前日から最終日まで、夜のパーティーも切れ目なくたくさんあってすごかった。
しゃべってきた
(photo by @koichiroo)
自分のトークのスライドはこちら。
動画ももう上がってた、すごい。内容で聞きたければこちらをどうぞ。YoutubeのサムネイルはなぜかDukeがデカデカと映っていますが、これは何かの間違いです。ちゃんとRubyKaigiのトークです。だ、だいじょうぶ……。
内容としては最近仕事でやっているプロジェクトにからめて、その仕事でのRubyの使い方と、ついでにRubyで分散ストレージシステム(のクローン)を書いてみたんだけどそこで感じたRubyに足りない機能、みたいなのをあれこれ話した。
使われかたがやっぱりWebに偏っているせいか、いざストレージミドルウェアを書いてみようとするとあれこれ面倒な書き方をしないといけない部分が多くて、これはもうちょっと言語機能としてどうにかしてほしい、みたいなのをあれこれ挙げたつもり。このへんが良くなるとTreasure DataとしてはRubyで書ける部分がさらに増えてすごく助かる。
で、ちょうどRubyコミッタの人達が周囲にゴロゴロしているので、これ幸いとそのあたりの機能のアイデアについてどう思うか聞いたりしてた。TDにもRubyコミッタは(何人も)いるが、どうしても同僚だとユースケースをよくわかっている分、意見に偏りが出る気はする……ので、外部のコミッタとあれこれ議論できる機会はすごく大事だと思う。
結果的にあれこれチケット登録したりパッチ書いたりしつつ、その内容を議論の結果をフィードバックさせてアップデートさせたりできてすごくよかった。
以下が自分が関わってて、Kaigi中に報告したり作成したり(過去に報告してて)コミットされて修正が完了したチケットとpull requestのリスト
- Uncaught exceptions may not be reported when Thread#report_on_exception=true and Thread#abort_on_exception=true
- 過去に報告してKaigi中に @n0kada さんのコミットにより修正された
- Method definition with explicitly callable method names
- 型! 型! このアイデアに対するMatzの意見を直に聞けた……なるほど感はあったけど、うーん
- Add a new method to create Time instances from unix time and nsec
- Kaigi中に報告して @nalsh さんによるパッチがKaigi後にコミットされた
- Idiom to release resources safely, with less indentations
- 数人のコミッタにもちかけてあれこれ議論したあとでチケット登録、話が進んでいる
- Add Queue#peek to fetch the head object in queue, without removing it.
- 必要なんですよ! みたいな話をしてとりあえずパッチは書いた、その後に実装とかも含めて議論して、うーん
- Add MonitorMinx#mon_locked? and #mon_owned? to check states of objects
- Kaigi中にパッチ書いて出しといたらするっとコミットされていた
ということで、RubyKaigi中とその前後だけでRuby trunkがだいぶミドルウェア書きやすい言語に向かって進んでいる。自分にとってはすばらしい会期だった。Yay!
行ってきた
参加者側としては、自分のトークが3日目だったのもあったし、周囲の人と話すのを優先していたといえばそう、なので、各トークは聞いたり聞かなかったり。しかし今年はとにかく型の話が多くて、自分も上記リストに含まれてる通りちょっと興味のあるトピックだったから、面白く聞いていた。
全体的にコードの話が多くて、というかコードの話しかなくて、いいなあと思う。ふらっと入ると面白い内容の話をしてるもんね。あとLTで @joker1007 さんと @k0kubun さんが勢いのある変態コードの話をしてて最高。その話、ふつうにメインのトークにproposal出そうよとは思うけど。w
ということで、おおむねふらふらとしつつ楽しんでいた。
で、まあお昼ごはんとかね、当然広島だからお好み焼きも食べるし、汁なし担々麺が名物らしいと聞いたらそれも食べるし。おいしかった。
夜はパーティーがあるとはいえ、当然その後もよさそうな店を見付けて飲みに行くわけじゃないですか。なんか*1異常に安いし、すごいおいしいんですよ。天国。
特にこの松茸と鱧の土瓶蒸しがもう大変にけしからん味で、この汁だけで日本酒がいくらでも飲める。けしからん。最高。最高でした。
Kaigi後の観光
せっかく広島まで行ったので、1日帰りを遅らせて2日間つくってちょっと観光してきた。広島城とか宮島とか呉とか。広島城はコンクリ城だけど、まあ、まあ。どっちかというと平城のくせに堀のあたりに風情あってよかった。
宮島は厳島神社は*2修学旅行で行ったのでほどほどにして、裏にある弥山をガッと登った。思ってたよりハードだったけど、なんとか。こちらも巨大な岩の下をくぐって山頂に到達するなど、独特の趣があってよい。
呉は雨が降ってたので、資料館をぶらぶらするのを中心に。街中の空中に突然潜水艦が登場するのとか、東京ではちょっとありえない趣がいいねえ。海上自衛隊の資料館をぶらぶらしたり、大和ミュージアムを回ったり、埠頭の展望台から馬鹿デカいコンテナ船が整備中(?)だったり建造中だったりするのを眺めて一人で盛り上がったり。いやー、港の街はいいなー。
で、観光したらおなかが減るじゃん?
もうね。
来年の仙台も楽しみですね!!!
Stream Processing Casual Talks 2 にいってきた & しゃべってきた
開催されたので参加してきた。
ついでにちょっと前に社内でのやりとりで思い付いてそのまま温めてたアイデアがあったので形にしてしゃべってみた。
しゃべってきた
社内でしゃべってたのは、ストリーム処理クエリって結局自分がもってるデータしか参照できなくて、じゃあ3年前のデータとJOINしたかったらクエリを3年走らせつづけないといけないの? 無理じゃね? みたいなあたり。過去データを参照したいケースは(特にビジネス要件のクエリで)普通に存在するいっぽう、現行のストリーム処理エンジンはクエリを開始した時点から受け取ったデータそのものしか参照できない*1
んで、いやよく考えたら過去データからストリーム処理クエリの計算中の状態を再現できれば、クエリ開始時に3年前からのデータをどこかからもってきて処理を起動できる、ストリーム処理系の脇にあるバッチ処理系を前提にしてこれができたらどんな過去データを扱うストリーム処理クエリも書き放題で、しかも好きなタイミングで再起動できるじゃん!!!! すげえ!!!! みたいなアイデアがある。SQLならできる。
これはいろいろ制約があって汎用の分散処理エンジンではなかなか難しかったりするのと、そもそもストリーム処理系とバッチ処理系で同じデータと同じ処理を前提に……これはラムダアーキテクチャというやつだ! ということでがーっとアイデアをまとめた。このアイデアの副産物として、同じ処理を内包する複数のストリーム処理クエリがオペレータ(とその内部状態)を共有できるというメリットもおまけについてきて、これが実現できればさらに基盤全体のスケーラビリティが上昇する。これはきた!!!!
まあ、分散処理版のNorikra(通称Perfect Norikra)はまだ1バイトも存在しないんだけど。スライド最後にちょっと書いたが、実は名前は既に決まっている。英語で普通の単語に聞こえ、かつユニークネスがある程度ありそうな単語を探すのは骨なので、プロダクト名が既にあるということはプロダクトを書き始めるまでの最大の問題が解決しているということだ。すばらしい。
いってきた
NiFiねー、とか、Hortonworks Streaming Analytics ManagerはUIよくできてんなー、とか。しかし全体としては面白技術という感じでもなくなって、みんな細かい改善がんばってんなあ、という気分。Apache Beamに乗るのはこれだから嫌なんだという感じがする。
ともあれ最近リサーチもサボってるので界隈の話題をひとさらいできてよかった。
*1:もちろんコードを何でも書ける処理エンジンであれば独自に外部データを参照してそれとJOINすることはできる、が、それはストリーム処理クエリがサポートしているとは言えないと思う。何でも書けるコードの中で勝手にやっているだけで、つまりそれはクエリ処理エンジンが分散をよろしくやれる範囲の外ということ。
System.nanoTime() をJMockitで乗っ取る
Javaでミドルウェアなど書いていると必然的に内部でタイマーを作ったりすることになってその制御に System.nanoTime() とかを使うことになると思うんだけど、じゃあテストどうしよっかと思うと、テストでは、まあ好きなタイミングで時間を進めたり好きなところで時間を止めたりってことが普通したいじゃないですか。どうすんのという話。
JMockitを使う
とりあえずJMockitを使っとけという話っぽい。static methodをモックできるのはこれしかない*1。
JMockitは理想的なモックフレームワーク - じゅんいち☆かとうの技術日誌
ちょいちょい調べると例が見付かるので真似る。
import org.junit.Test; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; import mockit.Expectations; import mockit.Mocked; public class FooTest { @Test public void testTimer(@Mocked System unused) { long current = System.nanoTime(); new Expectations() {{ System.nanoTime(); result = current; }}; assertThat(System.nanoTime(), is(current)); } }
JUnit4に怒られる
コンパイル通ったぜー、と思うとなんかテストでコケる。
java.lang.Exception: Method testTimer should have no parameters
調べてみたらこんなのが見付かった。要はJUnitよりも前にJMockitを書いておけと。えー、と思いながらとりあえず build.gradle を変更してみた。
dependencies { // jmockit should be written before junit // http://robertmarkbramprogrammer.blogspot.jp/2013/03/eclipse-and-jmockit-method-should-have.html testCompile group: 'org.jmockit', name: 'jmockit', version: '1.33' testCompile group: 'junit', name: 'junit', version: '4.11' testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3' // ... }
そしたら期待通り動いてしまった。えー。ジャバむずかしいな。
(追記) なんか期待通りに動かない
複数のテストケースでそれぞれ違う値でモックしようとしたら結果がおかしくなる……みたいなのが多発してこれはやばいということになった。調べるとJITが走ったらダメになるみたいな話もあるし、ここで苦労するべきではないと判断。撤退。
*1:今でもそうなのかな? まあ、たぶんそう
okhttpで非同期リクエストを実行するとき実行スレッドはどうなっているのか
複数のホストに並行してHTTPリクエストを送るコードをJavaで書く必要があって、Undertowをサーバに使ってるんでUndertowの http client でもいいかなーと思ってたんだけど、okhttpにも非同期リクエストの機能があるみたい。
ただパッと見て実行スレッド数の設定とかどうなっとるんや、というのが全くわかんなかったのでちょっとコードを追ってみたところ、以下のような感じのコードを発見しました。
呼び出し順としてはの以下ような感じ。
- OkHttpClient.newCall(Request req)
- Call.enqueue(Callback callback)
- RealCall.enqueue(Callback callback)
- client.dispatcher().enqueue(new AsyncCall(responseCallback)); ココ
なんで、この dispatcher() で返ってくるやつの正体を見ればどう制御してるかわかるだろ、って追ってみたらこんな。
https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/Dispatcher.java#L63
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/Dispatcher.java#L63}
ThreadPoolExecutorの引数は corePoolSize, maximumPoolSize, ... なので、なんかもう無限に(ではないけど)スレッド作る感じですね! 気合いが入っている!!
で、これはねーなと思いかけたんだけどちょい上のほうを見ると Dispatcher(ExecutorService executorService) というコンストラクタがあるので、外で設定済みのexecutorServiceを渡してDispatcherを作り、それを OkHttpClient.Builder.dispatcher() に渡して build() すれば設定済みクライアントが作れそう。
ちょっとめんどいな。
EmacsでJava開発をする @ 2017
お仕事でそれなりにまとまったJavaのコードを書くので、ちょっと真面目に手元の環境をアップデートしようと思ったらいろいろあったので、メモを兼ねてまとめる。なお手元の環境のアップデートにともない、次のエントリを大きく参考にさせていただきました。多謝。
qiita.com
なおそれまでの環境は EmacsでJavaを書く - nekop's blog を元にだいぶ前に整備したもので malabar-mode をずっと使ってたんだけど、Java8 Lambdaでインデントが崩れるとかいろいろあってちょっとストレスフルだったのが主なアップデート動機。
環境は OS X El Capitan *1 + Emacs 25.2 で、全面的に MELPA をつかってパッケージ管理やっている。
結論
最終的には ENSIME (の ENJINEモード) を使っている。これでコンパイルエラーを出すとかメソッド名を補完するだとかのエディタ上で最低限欲しい機能はだいたい揃った。ちょいちょい動かない機能とかEmacsを巻き込んでクラッシュする機能とかがあるけど、自分は元々エディタ上で操作を完結させたいとか思わない*2ので、まあこれくらいでいいやと。
その上でコードの自動フォーマットをチーム内で揃えるため、別途コマンドラインツールと git pre-commit hook の組み合わせで自動フォーマッタをかませることにしようとしている。これに使えるものもいろいろ考えたけど、IntelliJ IDEAにコードフォーマッタがあることを知ったのでこれを使うことにした。IntelliJ IDEAはエディタとしては使いたくないけど、コマンドライン起動するためにインストールするくらいはなんでもないぞ。
#!/bin/sh CURRENT_DIR=$(pwd) SETTING=$CURRENT_DIR/misc/airlift-codestyle.xml FORMATTER="/Applications/IntelliJ IDEA CE.app/Contents/bin/format.sh" exec 1>&2 # .java files to be committed files=$(git status --short | grep -E '^[AM] .*\.java$'| cut -c4- | sed -e "s|^|$CURRENT_DIR/|") echo "$files" | xargs "$FORMATTER" -s $SETTING echo "$files" | xargs git add
こんな感じのスクリプトを書いて pre-commit hook として使う。設定ファイルを与えられるんだけど、ここではPrestoとかでも使われてるAirliftの設定ファイルを与えることにしている。なぜか。まあ自分にとってもこのスタイルはそんなに悪くない。あんまり主張が強くないし。
問題はあって、なにしろ(Javaのコードを含んだ)commitのたびに裏側で IntelliJ IDEA を起動するので、遅い。あとなんかログとか警告とかも流れてきてちょっと邪魔い。けどcommitの頻度自体そんな高いもんじゃないし、まあいいかなと思っている。好きなエディタ使えるほうが大事。
ENSIMEの環境設定
正直、これはだいぶ大変だった。が、たぶん最適解が分かってればすぐ終わるんじゃないかなという気がする……。たぶん。基本的には Installation · ENSIME に従えばよい。あとプロジェクトが gradle を使っているので Gradle · ENSIME も。
が、いろいろ試したところうまく動かない組合せが多く、唯一動作したのは ENSIME 0.2.8 (stable) と gradle 3.3 の組合せだけだった。unstable (0.3.0-SNAPSHOT) を使うケースだと gradle 3.5 ではバグってて動かず gradle 3.4 では動くように見えるんだけど、Emacs側で melpa から unstable 版を入れて読み込もうとすると .ensime の形式がおかしいとか言われて動かない。
stable版はどうかと ENSIME 0.2.8 を使おうとしたら gradle 2.4+ との組み合わせだとバグってて動かないので gradle 3.3 までダウングレードしたら動いた。これを melpa-stable 経由でインストールした ENSIME 0.2.8 と組合せるとなんとか全体が動作するようになった。
今のところはバッファ開くたびに M-x ensime して有効にしている。しかしなんか同じプロジェクトのクラスを見付けてくれないことがあったり、build.gradleに設定してある依存ライブラリを見付けてくれないことがあるように見える。なんだかなー。まあ、自分にとってはそこまで致命的ではない。
Meghanada
他の選択肢は? というと、いくつか試してはいる。最初に試したのは Meghanada-Mode で、実際にこれはすばらしくセットアップが楽だった。が、最初に試したときは meghanada-mode を有効にしたあとで C-i したらEmacsごと固まる、という状況で、ちょっとこれは……という感じでいったんやめた。
その後に init.el の大掃除をしたあとでもう一度試してみたら動くには動いたんだけど、機能によってはやっぱり何のデバッグメッセージも無しに固まる症状がちょいちょい見られたりして、きびしい。あと手元ではコンパイル時警告が出てる場所の色付けみたいなのも動いてなかったので、機能として無いのか単に何かがおかしくて動いてないのかは知らないが、あまり便利とは言いがたい状態だったのでいったん使用をやめた。
何しろインストールが楽なので、使えれば使いたいんだけどなー、という状況でした。コードの自動フォーマット機能があるのはすばらしいけど、フォーマッタをどうにかして設定できないかなあ、という気もする。
今後に期待。
Google-java-format
エディタはどうにかするとしてコマンドラインフォーマッタはどうか、と思って見付けたのが google-java-format なんだけど、デフォルトのコーディングスタイルがちょっとアレすぎて使うにはアレ……。2スペとか。
なお --aosp というオプションが最近のバージョンで足されてて、これはどうも Android Open Source Projects 用らしいぞ、ということで試してみたら4スペだし、これはこれでいいかなと思いかけたんだけど、やっぱりちょっとクセが強い感じがした。主張つよくバリバリ修正されるんだけど、改行をやたらアグレッシブに入れるし、その割にここ改行しないんだ? みたいな納得いかない部分がちょいちょいあるし、lambda使ったときにインデントがやたら深くなる傾向があるし、ということで、きびしい。
途中まで使いかけたんだけど IntelliJ IDEA のフォーマッタを見付けたのでそっちに行った。
まとめ
がんばるしかないがちょっとつらい。
(5/29追記)
前のスクリプトだと modified だけど add されてないファイルまでpre-commit hookがgit addしちゃってたのでちょっと直した。
「そのデータ分析基盤、作るか? 使うか?」という話をしてきた
ファンコミュニケーションズさんが自社オフィスで勉強会をはじめるということで、第1回でしゃべりませんかというお誘いを受けたので参加してきた。お誘いありがとうございました!
内容はどうしようかなと思ったんだけど、相談してみたら過去のこのエントリのスタートアップ限定じゃない話とかどうかというものがあり、自分でも面白そうだったので、そんな感じで内容をまとめてみた。
まあトピックと自分の現在の勤務先からしてどうしても内容にバイアスがかかると思われるのはしょうがない……ので割り切った宣伝スライドはまあ1枚入れるとして、それ以外はいちおう公平な議論を目指したつもり。
で、中にも書いてあるけど、明確な結論なんてものはなくて、各社個別の事情にあわせてやりましょう。ただしこのくらいはどうせやらないといけないから、作りだす前に考えといた方がいいですよ、という内容になっている。まあ、やっぱり大変なので大きな企業で数十人単位のチームを作ってやってるところが多いですよねー、という。
やらないといけないことのバリエーション(==complexity)は企業の大小によらないので、どうしてもあまりエンジニアリングチームの大きくない会社だと難しいんじゃないかなとは思います。
とまあ、そんな感じのことをしゃべったり、DokkuやAkkaの話を聞いたり、あとは参加者の人のデータ分析関連のお悩みを聞かせてもらったりで、楽しいイベントでした。