最近のruby-core (2016年9月)


こんにちは。卜部です。

ruby-coreというRuby本体の開発の議論がされているメーリングリストがあります。

新機能やバグ報告などがだいたいここに集約されてくるので購読しておくとRubyの動きが分かります。

最近興味深かったトピックを紹介します。やっぱり一回ぶん間が開いて今回は量が大変でした。過去分はこちら

[#8526] gemify tk

ずっと前からrubyについてきていたtkというライブラリが、紆余曲折ありながらもgemに切り離されました。もちろんtkが書かれた頃はgemなどというものはなかった。そういう意味では長い間使われていたライブラリが時代とともに立場を変えていくという話かなとおもいます。

これに限らず、長期的にはrubyの標準添付ライブラリは徐々にgemに分離していくことで年々数を減らしているという趨勢になっています。

[#9612] Gemify OpenSSL

今回OpenSSLのRubyバインディングもgemになっています。とはいえ、こちらはちょっと事情が違って、というのも、そもそもSSLを使わないとそもそもgemをダウンロードしてくることができない。なので鶏卵になってしまうので、gemになったとはいえ、公式のソースコードをコンパイルすることでopensslというライブラリはこれまで通り作られてきます。

じゃあ何が嬉しいかというと、openssl gemに脆弱性などが発見された場合にruby全部入れなおさなくても、Gemfile書き直してbundle updateだけで済むというところですね。これは運用上おおきな利点になるはずです。

[#11818] Hash#compact

あ、これ最後がまつもとさんの”Accepted.”で止まってる。

誰か実装すればすぐ入るんじゃないでしょうか。

[#12508] Integer#mod_pow

提案されているmod_powというのは**%で、%の法がじゅうぶんに小さければ**してから%するよりもずいぶん高速に処理できるらしい(ざっくりの議論でいうと、**は急速に数が大きくなっていってメモリを大量に消費するけど、%の法が小さければ最終的に出力される数も小さいので、上のほうの桁をそんなに計算しなくてもいいはずってことですよね)。

とはいえmod_powという名前はどうなんだろうという議論は当然あるところで。Pythonを見ると、math.pow(x, y)という形なので、powという名前はどうだろうという提案が返されているところですね。

[#7882] Allow rescue/else/ensure in do..end

do beginとか面倒だから省略したいつまり

foo do
  # ...
rescue
  # ...
  # ...
end

と書きたいというニーズなんですが、問題はdoの時はそれでいいとして{…}と破滅的に相性が悪い点なんですよね。自分が知りうる限りでは波括弧を使うプログラミング言語ではもれなく try {…} catch (…) {…} 式のシンタックスになっているので、{…}式のブロックにrescueをつけるとすると当然、ブロックを分けざるをえないと思うんですけど、それはスコープの面でかなり大掛かりな変更を入れないといけない。実装というより考え方が変わると思うんです。

[#12624] !== (other)

!=みたいな感じで===の反対をする演算子が欲しいという提案です。用途としては

def foo(arg)
  raise "wrong arg" if ! (String === arg)
  ...
end

みたいなことをみんな書いてる(grepするとわらわらヒットする)し、みんな必要なんだからもう入れよう、ということですね。

ところがこれはまつもとさん的にNGで、ようはそういうクラスによる場合分けはそもそもよくない。もっとクラスじゃなくて実際の振る舞いで分岐してほしいし、少なくともクラスの場合分けを今より書きやすくするのは間違った方向だ、ということでした。

たしか口頭で「そうはいっても、設計者の意図にかかわらず、人々はすでにクラスで分岐しちゃってる現実がある。それに気づくという学びがあったんじゃないですか」と指摘しておいた記憶があります。

[#12625] TypeError.assert, ArgumentError.assert

これも同じ理由でNGで、TypeErrorを出すようなのを書きやすくしたくないんだそうです。

[#12490] Remove warning on shadowing block params

変数のshadowing(内側のスコープで外側のスコープで使ってるのと同じ変数名の別の変数を定義してしまって外側の同名の変数が見えなくなること)に対する警告はもう、うるさいだけだからやめようという提案です。

個人的にはいいと思うんですが、これもまつもとさん的にNGで、ようはshadowingはそもそもよくないんだと。推奨したくないという思いがあるようです。

[#12573] Introduce a straightforward way to discover whether a process is running

もともとの提案されているニーズとしてはpidファイルを読んでそれが生きてるか監視したいということのようですが、どうにもそもそもpidファイルっていう設計が怪しいんじゃないかという話があって、ようはpidというのは使われなくなったら再利用されるわけです。なので、もし生きてるか判定したいという話になると、そのpidに「なにか」が動いていることが分かったとして、それで本当に当初想定したプロセスが死んでないかは、よくわからんのですよ。

なので、提案されている「pidを引数にとって、それが生きてるか判定する」という機能は、その字面の要件を満たすものは、たぶん問題なく実装できてるのだが、問題はたぶんそれ、意味ないよねという点にありそうです。

[#12489] hppa problems on Debian GNU/Linux

おそらく主に基幹系とかで一定の納入実績があったと思われるが、ほとんどのプログラマが実物を所有したことがないし、今となっては終売どころかサポートも終了している、hppaという古いプロセッサアーキテクチャにおいてrubyがコンパイルできないという報告ですが、厳しい…

このチケットの報告者もdebianのバグトラッカーをコピペしただけ(ありがたい話ではあるけれども)で、hppaに詳しいわけではないとのことで、みんなで「どうなんすかね」と言い合ってる状況です。

[#4897] Define Math::TAU and BigMath.TAU. The “true” circle constant, Tau=2*Pi. See http://tauday.com/

どうやら、円周率πのかわりに半径と円周の比τを使うことできれいになる公式がいろいろとあるので、πのかわりにτを推進していきましょう、という、どこまで本気かわからんムーブメントがあるのですね(上記URLに動画などあり)。パスタファリアンみたいな感じ?で、もちろん「おまえは何を言ってるんだ」的な反応もありつつも、とりあえずべつに有害でもないので、取り込まれもせず放置されていたのですが。

どうもなぜか今年の8月になってGuidoが急にやる気をだしてPython 3.6に突如定数math.tau突っ込んでしまったという事件があったようです。理由不明。Redditも大盛り上がり

というわけでなぜか急にこちらにも「で、Rubyはどうするの」的なのが来てて、決めなきゃいかんのかー面倒なーという感じです。

個人的には別に否定したいわけでもないのだがさりとて肯定したいわけでもなくとりあえず放置がいちばんよかったんだけどな

[#12593] Allow compound assignements to work when destructuring arrays

たとえば+=のような、日本語では自己代入演算子とよばれる一連の演算子があるのですが、これらは代入の見た目をしているにもかかわらず、多重代入に対応していません。で、対応して欲しいつまり

a, b += [ c, d ]

のように書きたいという提案です。

でも面倒なんですよね。よけい混乱すると思う。ただでさえ多重代入は控えめに言ってもカオスなわけでして、そこにさらに自己代入を追加しても。たとえば

a, b += 1, [2], "3", :'4'

こういうのをどう解釈すればいいのか。もちろんなんか振る舞いをひねり出すことはなんらかの形でなんらかなったとしても、それで誰もが納得できる形には到底ならないと思うんです。

[#10594] Comparable#clamp

これは無事に入った新機能です。Rubyの整数は幅に制限がなくてすごく大きな数も扱えるのは便利ではありますが、そうはいっても実際の用途としてはある程度のレンジにおさまってないと困るというニーズは当然あるのですね。たとえばグレースケール画像であればピクセルのグレースケールは当然ある範囲におさまってないと画像として成立しない。

というわけで範囲からあふれたときによしなに範囲内に戻してくれる機能が求められて、今回clampという新機能として入りました。

[#12648] Enumerable#sort_by with descending option

悪くないんだけど今一歩という感じの提案。ソートするメソッドに逆順が欲しいという、それ自体はあるいみで分からなくもない提案なのですが。でも「複数のソートキーを指定したい時」という話がついてきていて、これはちょっと話が大きくなりすぎている気がするんですね。もともとそういう話はなかったところなので。

ただまあ、実際SQLならORDER BY title ASC, updated_at DESCとか書けてたやつがRubyでやろうとするとあんまりうまくないぞ、というのは問題意識としては理解できるところではあるので、なんらかのシュッとした新機能にできるとはいいと思うんですが。既存のやつの拡張としては惜しい感じというまとめのようです。

[#12353] Regression with Marshal.dump on ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

はいはい、と思ったら意外に根が深かった。特定のバージョンのActiveSupportにおいてMarshal.dump(1.day) とかが動かなくなってしまったという現象があって、まだ実は2.3系だと直ってないです。実はmethod_missingとrespond_to?というふたつのメソッドは対になっていて、再定義するなら両方を矛盾ないようにきちんと書かないといけないんですけども、そうなってない時に壊れてしまう。

いや、まあ、下手に書かれたプログラムが下手なりに動くのを妨げない(自分の足を撃つのを止めない)という方針はあるわけですが、それの巻き添えで本体側の処理が壊れるのはやっぱ良くない。

というわけでMarshal.dumpが壊れないようにと修正が一旦は入ったんですが、それはそれで悪影響が出たので一旦revertされてしまったという現状です。比較的新しいバージョンのrubyとActiveSupportになってれば問題が起きない現状になっていますが、特定の組み合わせでは問題が継続中のようです。

[#9451] Refinements and unary & (to_proc)

今回Refinementsまわりでいくつか動きがあるんですが、まずこれは&:fooとかやったときにRefinementsが効いてないように見えるのはバグでは、という話です。

とはいえ、もとの動きは&というのはSymbol#to_procを呼ぶような仕組みになっていて、Refinementsの側でto_procが上書きされてないから、動かないのはもともとの動きとしては「そういうもの」でした。

なのですが、最近は&については、もう裏で変換が走ってるとか誰も気にしてないし、&:っていう演算子であるかのようにみんな勘違いしてるので、まあ、支援してあげてもいいんじゃないですかということになったようです。

[#11476] Methods defined in Refinements cannot be called via send

似たような話でsendもsend自体を上書きしないとsend自体が実装されてる場所の定義を見に行っちゃうわけですが、それも変えましょうということになりました。

[#12689] Thread isolation of $~ and $_

これすごい細かい話なのですが、$_とかのPerl由来としか言いようがない一部の不幸な変数、これらは実は$とかついてるくせに今だと普通のローカル変数になっていて、ローカル変数と同じ領域で保存され、同じアクセスをします。

というふうに教科書的には記述されると思うんだけど、実はスレッドが絡んだ時だけまたちょっと違って、実は

  • グローバルトップレベルの$_は、スレッド間で共有しない(スレッドローカル変数)
  • そうじゃないメソッドの中とかで出現した$_は、スレッド間で共有されうる(ふつうのローカル変数)

ということになっている。なんで? という感じですが実装者いわく「いや、1.8にあわせただけだし」とのことなので真相は良くわかりません。がともあれそうなってる。

で、これは意味がわからないのでどちらかに寄せようという話はもっともなのですが、それよりも、そもそも、Perl由来としか言いようがない一部の不幸な変数はもう捨てたほうが建設的なんじゃないですかという話になるわけです。現状では不幸しか産んでないだろ。

ただ、じゃあ今すぐ$_を消せるかというと、世の中にはString#gsubというどうしてもこの不幸を回避することができない不幸があって、ここをどうにかしないかぎりは実はなくせないのですね。このあたりは古くからある上に痕跡器官というにはあまりに多用されてるからしょうがない面はあるのですが。

というわけで実質的にはこの件は#12745がブロッカーになっているという感じかと思いました。

[#12512] Import Hash#transform_values and its destructive version from ActiveSupport

以前から紹介していたこの機能は結局紆余曲折の結果Hash#transform_valuesっていう名前で入ってしまいました。Parturiunt montes nascetur ridiculus musという感がありますが、ともかく新機能ですね。

[#12695] File.expand_path should resolve ~/ using /etc/passwd when HOME is not set

expand_pathは現状でも~root/とかを展開するためにgetpwent(3)(かそれに相当するやつ)を呼んでいるのですが、~/を展開するときは環境変数の$HOMEを採用して、getpwent(getlogin())的なやつは呼びません。でもやってもいいんじゃない? $HOMEはなんらかの事情でセットされてないことがあります。たとえばsudoできつめの制限してる時とか、env -iとか。環境変数を制限した状況でも動くのは悪い話ではない。

とはいえ、~ってのはもちろんシェル由来なわけですけどじゃあシェルではunset HOMEしたらどう動くのか。というのは興味のあるところですが、どうもbash(環境変数なくても展開する)とzsh(しない)でも動きが違うとのこと。POSIXは意図的にここは定義してなさそうです。

というわけで便利な方に倒せばいいんじゃないのというふうに思いますね。

[#7418] Kernel#used_refinements

名前が変わってModule.used_modulesという名前になったのですが、ともかくこの機能は入りました。呼んだ瞬間に使われてるRefinementsの一覧が戻ってくるやつです。

これも自分としては何に使うかあまりしっくり来てないんですが、想定される用途としてはデバッグとかデバッガの中とかそういう場合に使うのではということでした。

[#12086] using: option for instance_eval etc.

これ議論中でちょっとまだ入るかわからないんですが、instance_evalなどにusing機能を持たせたいという話です。

Refinementsって今あんまり用途ないじゃないですか。それってなぜかというとDSLの実装として使われるつもりで当初設計したのに、DSLの中でusingとか毎回書けないわけです。トップレベルにしか書けないから。でもDSLってだいたいinstance_evalの中でやりますよね。作るときね。なので、そこをなんとか便利に使えるようになってほしい。というニーズです。

ただこれには大きな問題があります。instance_evalにはブロックが渡せます(そりゃそう)。つまりたとえば30時間前にどこかのライブラリの奥深くで作成された謎のブロックをもちまわってどこか全然別のライブラリの奥深くとかで呼んでもいいわけですよ。なのに、そこにRefinementsを「後から」注入することが可能になると、どういうことが起こるか。

ようはブロック書いた時とそれを呼ぶときで全然違う振る舞いになりうるわけです。振る舞いが静的に確定しなくなる(==キャッシュできなくなる)。それに、同じブロックに対して何回もRefinementsを(ひょっとしたらマルチスレッドで、左右から同時に)あてることができてしまう。それって順序とかどうなるんでしょうか。

というわけで、デザイン、そして、処理速度の面で問題が指摘されています。

[#12694] Want a String method to remove heading substr

文字列の後ろから文字を取り除きたいというのは、たとえば改行文字などに需要があるので、chompというメソッドで実現できますが、逆に、先頭から削るというのは、今はない。

ところが最近ではfluentdというアプリがあって、このアプリではデータにタグというものを付けたり外したりすることがあるそうですね(伝聞調で言ってますが、あります。知ってます)。たとえば"foo.bar.baz"から"bar.baz"に変換したいが、 "bar.foo.baz""foo.baz"になってしまうのは困る、ということとかがあるわけです。

というわけで需要は理解するのですが、どのように実現するか。たとえばPythonだとstring.lstripの第二引数になにやら指定できるので、これかという気になるわけですが、よくよく調べてみるとどうも今欲しいような機能ではないらしい。PHP、Elixirもなんかちがう。GolangのTrimPrefixというものが、どうやら似た機能のようです。

というわけでこれは既存のメソッドの改修ではなく新規メソッド追加で実現する方向なんじゃないかと思われます。ただ、そうなると、名前がな…

[#12690] Improve GC with external library that may use large memory

拡張ライブラリのAPIに新たに加わった新機能です。

これまで、RubyのGCは自分で確保したメモリが足りなくなったら走るという基本戦略でやってきましたし、今後も基本はそうかと思います。拡張ライブラリの作りによっては、rubyじゃなくてライブラリの中で動的にバッファなど確保する場合もままありますが、そういう領域はGCでは管理しきれません。

とはいうものの、このままだとGC側からは40バイトのオブジェクトに見えているものが、実は裏では200MBのバッファを確保したままゴミとして放置されてます、といった状況があり得るわけで、ようはGCのプレッシャーが過小評価されている。本来は、拡張ライブラリが使っているメモリ領域もきちんと計上していくべきなわけです。

なので、今回は拡張ライブラリの側でメモリ管理するのはもちろん従来通りですが、ともかく確保したサイズだけは通知して欲しい新関数rb_gc_adjust_memory_usageが追加されました。これを呼んどくことでプレッシャーの過小評価は回避できるようになります。

[#12548] Rounding modes inconsistency between round versus sprintf

irb(main):001:0> sprintf('%1.0f', 12.5)
=> "12"
irb(main):002:0> 12.5.round.to_s
=> "13"

この違いはなんなのかという話です。これは実は丸めモードというやつで、ちょうどなんとか.5のタイブレークになってるときにどっちに寄せるかという問題になっている。sprintfが実装しているのは規則A(別名banker’s round)という方式で、roundのやつは規則B(別名四捨五入)という違いがあります。

で、不揃いなのはへんだから統一しましょうというのと、roundはオプションで丸めモードが選べてもいいよねというのは、まあ穏当な落としどころかと思うんですが、どっち向きに統一するのかの点で意見があります。

  • 算数で習ったのは四捨五入だから四捨五入がいい。Rubyは(負の数の)剰余の定義などいくつかの点で「算数で習った」を重視している。ここもそうすべきだ。
  • タイブレークの微妙な挙動なんか99%の人は気にしてない。気にする人が要求するのはおおむねbanker’sなので、気にする人が文句言わないbanker’sにしとけば気にしない人は気にしないから全員幸せになる。

筆者卜部の意見は後者ですが、どうなることでしょうか。

[#12700] regexg heredoc support

一言でいうと正規表現のヒアドキュメントが欲しいという要求です。要求そのものはwell-definedだし、機能として実現可能かというと、できるとおもう。

ただ何に使うかよくわからんのですよね。この提案を受けてから自分でも正規表現を使うケースなどがあったのですこしこの提案されてるリテラルを使おうとしてみたのですが、だいたい使わないんですよ。なんでかというと、正規表現リテラルってのは基本的に長さが長くなるにつれて読みづらさが非線形に増幅していくので、そもそもあんまり長い正規表現を書きたくないモチベーションというのがすごい大きくて、普段は正規表現は短ければ短いほどいい。なのでヒアドキュメントにするほど大きい正規表現を書きづらい。

一方でそのモチベーションを突き抜けてまで書かざるをえない重厚長大な正規表現の場合はとっくの昔に読みづらさが臨界点を突破しているため、リテラルでメソッド引数にポンと渡したりしても無理で、というかリテラルをエディタで書いてる途中にすでにメンテ不能。なのでやはり適切に変数なり定数なりに代入して、なにをする正規表現なのかをくわしく書いておかないと無理なわけです。

というわけであんまり需要ないんじゃないかという気がしているんですが、どうでしょう。

[#12402] Inline rescue behavior inconsistent for method calls with arguments and assignment

一方でこの文法は議論の結果無事新しく採用されたもので、

x = y z rescue w
# 1. (x = y(z)) rescue w
# 2. x = y(z rescue w)
# 3. x = (y(z) rescue w)

この式、3通りくらい解釈があり得ると思うんですが、もともと1だったのはさすがにちょっとおかしいということで、今は3になりました。

[#12299] Add Warning module for customized warning handling

新機能です。ひさしぶりにコア組み込みのトップレベル定数が増えてます。

ようするにいまRUBYOPT=-dすると山ほど警告出るじゃないですか。でも本当は自分の書いたコードの部分は無警告なんだけど、意識低いライブラリとかが警告たれ流しにしてて、自分のほうは意識高いのに、他人の意識低さがフラストレーションになってしまう。

そこを回避するためにとりあえず意識低いライブラリの警告などは黙らせたいわけですけれども、これまでは警告全部黙らせるか、諦めてだだもれにするかしかなかった。もう少しコントロールしたいんだけど、という需要が満たせてなかったのですね。

というわけで今回新設のWarningという定数が増えています。すべての警告類はこれのwarnというメソッドを経由して出力するように変更になっています。これを上書きとかprependすることで挙動が変更できるわけです(IOの出力が最終的にwriteに帰着するからそこだけ上書きすればいいのと同じ)。

今回コアに作り込まれた機能だけでは元々の問題であるRUBYOPT=-dがうるさい件を回避するためにはまだフィルタ部分が不足しているのですが、すでにこの機構を使ってフィルタリングを実現するwarningというgemが作られています。

最後に

マネーフォワードでは、ruby-coreが気になって夜も眠れないエンジニアを募集しています。
ご応募お待ちしています。

【採用サイト】
マネーフォワード採用サイト
Wantedly | マネーフォワード

【プロダクト一覧】
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Android
クラウド型会計ソフト『MFクラウド会計』
クラウド型請求書管理ソフト『MFクラウド請求書』
クラウド型給与計算ソフト『MFクラウド給与』
経費精算システム『MFクラウド経費』
消込ソフト・システム『MFクラウド消込』
マイナンバー対応『MFクラウドマイナンバー』
創業支援トータルサービス『MFクラウド創業支援サービス』
くらしの経済メディア『MONEY PLUS』

Pocket