Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

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

こんにちは。卜部です。

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

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

最近興味深かったトピックを紹介します。過去ログはこちら

[#12039] Fixnum#infinite?/Bignum#infinite or Numeric#infinte, consistent with Float#infinite? and BigDecimal#infinite?

以前も紹介したかと思いますが、Float以外の数値的なやつにもfinite?といったメソッドが追加になりました。より統一的に扱えるようになります。

[#11195] Add "no_proxy" parameter to Net::HTTP.new

環境変数でno_proxyというものがあるわけです。これはなにかというと、設置環境がproxyを経由しないと外に出て行けないため、proxyを設定しているけれど、とはいえローカルのたとえば192.168.0.1とかにそれはそれでイントラネットのサーバが動いていて、それにアクセスするためにはproxyにアクセスしてはいけないとかいうケースです。そういうときのために「(普段はproxyを使うけど)このホストにはproxyを使わないでほしい」という一覧を指定できる。これは別にrubyだけじゃなくて、一般的に使われているものです。ただなんとなく空気でそうなってるのか、それとも明確な規格があるのかよくわからないですが、少なくとも1995年時点でMosaicは実装してるぽい文献がヒットするので20年以上の歴史はあるぽい。

で、環境変数を解釈するのそれはそれとして、とはいえ環境変数はグローバルだから、Rubyスクリプトの内部からはなかなか扱いづらいものがあるわけですよ。たとえば書き換えようとすると副作用が予測できないから、かなり面倒。なので、本件の要求のようにコネクション単位で指定できるようになっていると嬉しいというのは理解できなくもない。

ただ問題は、Net::HTTPというのはHTTPをハンドリングする中では一番低層、セッション層の直上のレイヤーなわけです。だから、本来はNet::HTTPというのはさらに上位層のライブラリがそういうno_proxyとかそういうのは適切に解釈した後に実際にTCPとかSSLとかのセッションを開きに行くところという設計なわけ。no_proxyみたいなのを置くには適切な場所ではない。低層をリッチにしてもろくなことがないし。

とはいうものの、じゃあ逆にどこに置くんだよという話になるとあんまり適切な場所がなくて、Faradayなのか? というとそれもなんか違う(標準で実装されるべき機能ではある)し、あんまりどこに置けばいいかというのがよくわからんという現状ですね。どうしたものか。

[#6783] Infinite loop in inspect, not overriding inspect, to_s, and no known circular references. Stepping into inspect in debugger locks it up with 100% CPU.

さまざまなところでinspectというメソッドが呼ばれることがありますね。便利です。

が、このメソッドはオブジェクトを文字列に変換するんだけど、オブジェクト同士の参照をたどって全部文字列にしようとするから、複雑なネットワークを構成している場合にすごく大きな文字列になることがあります。一応、ネットワークのループは検出しているから、無限に長い文字列になることはないんだけど、DAGになっているときに強烈な感じになることがあって、何百万文字も生成するからプロセスが何秒も戻って来ないとかいうことになる。

とはいえ、inspectが使われる時といえば、たとえば例外のメッセージの中で表示するとか、そういうやつでしょ。つまりだいたいは人間が読む前提があるわけですけど、じゃあそんな何百万文字も生成された出力をまともに読めるのかという。まあ無理じゃないですか。

このチケットで言われている問題意識は上記のような話ですが、人間が読むという意味では他にもinspectには何件か問題が指摘されていて、たとえば文字列がぶっ壊れてる時にぶっ壊れたままだと困るので適当にエスケープする機能があるんですけども、とはいえたとえば壊れてるんじゃなくて漢字とかならエスケープせずにそのまま表示してほしいじゃないですか。そのあたりの処理が、いまだとグローバルな設定など参照しつつマジカルな感じに動くんだけども、やはり本当はきちんと出力先エンコーディングを指定できるべきで、しかし指定できない、とか。

というわけで一段メタに考えると、inspect :: Object -> String 的なAPIシグネチャになってるのがそもそも間違ってたということができて、たとえば設定を渡せないといけないとか、出力先のバッファを持ち回れば中間文字列の生成が抑制できるのに、とか、そういうのが様々に足りていないというのが、本来解決すべき問題ではないかという話になっているわけです。

ただ、そこまで行くとかなり話が大きいため、なかなか手が動かないというのが問題。

[#12760] Optional block argument for itself

オブジェクトをブロックに渡して、なんか変換させた後、そのブロック評価結果を返すメソッドつまり

class Object
  def 某方法名字
    return yield self
  end
end

こういうやつ。

で、問題は、「某方法名字」に適切な名前を誰も思い浮かぶことができてないという件なわけですね。本当に色々な提案がされているけれど、どれもしっくりこない。これに限らず設計というのは名前が決まればだいたい終わったようなものなわけですけれど、この例は特に、あと名前だけ決まらなくて困っているというやつのうちでも典型です。

今回はitselfという名前が提案されているわけですが、それだとなんか変換結果じゃなくて、変換前のオブジェクトが返ってきそうに感じてしまうので違和感があります。

[#12732] An option to pass to Integer, Float, to return nil instead of raise an exception

これはどちらかというと、整数に変換できなかった時にnilになるto_iが欲しいという話です。

いま、たとえば "foo".to_iとかが0を返します。これはでもたとえば逆に(0/0.0).to_sみたいのでもともかくなんか文字列を返すというのと同じ話で、to_i :: String -> Integer 的なシグネチャになっているという以上の理由ではないのだけれども、とはいえなんか無理矢理変換されている感は否めない。

で、じゃあ他に変換はないのかというと表題にあるようにInteger()というのがあって、これだと変換できなかったら例外になるわけです。が、例外というのもそれはそれで話が大きくて、もうちょっと手軽になってほしい。

ようは某方法 :: String -> Either Integer NilClass 的なシグネチャになっているやつがほしいという話ですね。理解できなくもない。

ただそれを実現するのにIntegerを拡張するのがいいかどうかは議論のあるところかと思います。別建てにしてもいいのでは。

[#12142] Hash tables with open addressing

このHashの内部構造を変える話、そろそろ入れるって毎回書いてるけど入らないですね。どうなるんだろう。ひょっとしてこの記事が公開されてる頃にはすでに入ってるとか?

Symlinkごしのファイルを読み込もうとした時にrequirerequire_relativeでSymlinkを解決するかしないかが違っていて、同じファイルが二回読まれてしまうことがあるので、困るという報告です。それ自体は、バグだとは思うし、修正すべきということ自体には異論がある人はいないとおもう。

ただrequireがSymlinkを解決しないのは意図的なんですよね。なんでかというとrequireってrequire 'json'だのrequire 'active_support/core_ext/hash'だの本当に山のように呼ばれるので、初回ファイル読むのはしゃあないとしても二回目以降の呼び出しでも毎度readlink(2)呼びまくってると都度ディスクアクセスが走ってたいへん非常に辛くなっちゃうんです。なのでrequireは二回目以降はディスクアクセスが完全に回避されるようになっている。

一方でrequire_relativeは「relativeとは何か?」というのを解決するところで自動的にディスクアクセスは不可避だし、jsonとかそういうのみたいに「わからんけどとりあえずrequireしとくか」みたいな使い方でもないので、こちらは問題なく解決されているわけです。なのでここに違いが生じている。

というわけで理由はそうなんですが、さておきバグはバグだと思うんで改修すべきですがどういう方向で解決すべきかよくわからない。一応、「Symlink解決したやつとしないやつと両方$LOADED_FEATURESに突っ込んどけば」という提案はあるんですが、それは逆にスクリプトからunloadしたくなったときに殺しきれなくなって困るという反論が出ていて、まあたしかになあという感じです。

[#12745] String#(g)sub(!) should pass a MatchData to the block, not a String

Ruby初心者FAQでgsubの第二引数に$1とか使っちゃって動かなくて「第二引数じゃなくてブロックにしましょう」というのは鉄板中の鉄板ですね。

しかし$1というのはPerl由来のなぞの振る舞いをする特殊変数で、なぞなので、なぞなことになる。たとえばgsubをラップして俺函数を作ろうとすると、これはうまくいかないわけです。

zsh % bundle exec bin/rails console
Running via Spring preloader in process 69534
Loading development environment (Rails 5.0.0.1)
irb(main):001:0> str = ActiveSupport::SafeBuffer.new('xbbb-xbbb')
=> "xbbb-xbbb"
irb(main):002:0> str.gsub(/x(?b+)/) { $~[:hoge] }
NoMethodError: undefined method `[]' for nil:NilClass

まあようはPerl由来のなぞな振る舞いが、設計当初として便利だったのは否定しないんだけど、さすがにもはやRubyとPerlは別物なので。いまどきのRubyの用途にはあんましフィットしてないわけですよ。

そういうわけで$1とかに依存しないように、ブロックに明示的にMatchDataを渡せばいいよねという提案なわけですけれども。もちろん、それで解決するといえば解決するわけですが、さりとてAPIが変わってしまうので、gsubという、マイナーと言ってしまうにはあまりに使われまくってるAPIに変更を入れるというのは、のきなみに動かないスクリプトばかりになることがあからさまに見えてるから、そこをどうするかで悩ましいという感じです。

一応、方向性としては、別名の新しいメソッドを作りましょうという提案と、既存のブロックパラメーターはそのままで追加のパラメーターを増やせばいいじゃんという提案がいまのところ有力ですが、どちらも一長一短だねというコメントがついていて、決め手に欠ける感じになっています。

[#12845] Do we need libruby-static.a?

ついに.aファイルがインストールされないことになってしまいました。

プロジェクトが始まった頃にはまだ動的リンクという考え方自体の勃興期で、すべてのファイルが静的にリンクされている状態だったわけですけれども、その後いったん、.soなら.so.aなら.aのどちらかを作るようになった(いつかわからんけど98年くらいの頃か?)後、2002年ごろに[ruby-dev:18700].so.aの2種類のライブラリを作るようになって、長くその状態だったわけですが、でもまあ今時は動的リンクは当たり前だし、そうなってくると両方あるのは無駄というのは、まあそうかもしれませんが、とはいえ時代の変遷ですよね。十数年を経て20世紀の挙動に戻ったという。

なお今の段階だと明示的にconfigure --disable-install-static-libraryとすることでこの古くて新しい挙動を試せるようになっています。

[#12781] Segmentation fault on macOS Sierra (sqlite3_adapter.rb)

これひどいんですけど、Macの更新したらSQLiteが動かなくなったという同案多数の報告が多数来てて軽くDoSのような感じになっているのですが(duplicated issuesの多さが物語っている)、もちろんMac側を変えたら壊れるのがRubyのせいのはずはないんですよね。

ところが事態はさらにひどくて、時を同じくしてPythonにも同様の報告がされているんですがそちらにて解析されている内容によると、どうやらAppleはSQLiteに勝手にGCD対応のパッチを入れていて、それのせいでSQLiteが壊れているんだそうで。SQLiteのせいですらなかった。

というわけでOSにバンドルされてるやつを窓から投げ捨てて各自で公式から取ってくるのが正解らしい。SQLiteもRuby(もPython)も公式同士で組み合わせればちゃんと動くという現状ですので、そのようにしてください。

[#12664] Multiline pretty-printing of multiline strings

今回ppの出力が変わりました。これまで文字列はppしても横に長くべったり出力されていましたが、今回文字列に改行が入っていたらそこで分割されるようになりました。

まあppの出力は人間が見る用なので、読みやすくなるのはいいことなんじゃないでしょうか。

[#12849] Ruby 2.3.1 build fails with FreeBSD 11

上にMacの話を紹介してますが、それはそれとしてFreeBSDにも別の問題があるらしくて、どうもpkgでopensslを入れると、コンパイルするときはそっちの/usr/local/includeに入ってるヘッダを見ながらコンパイルするのに、実行時にはマジカルにbase systemに入ってるほうの/usr/lib/libssl.soをリンクしちゃってへんなことになる、という挙動があるらしいです。

回避方法はあって、ようはopensslを何個も入れるのが根本的にどうなのかという話と、もし入れるならちゃんとrpathを固定すればいいわけですけれども、そうはいうものの普通の人は rbenv install とかでインストールするから、普通に入れると普通にはまる事態になっているということのようですね。

最後に

マネーフォワードでは、return yield selfの名前を考えてくれるエンジニアを募集しています。 ご応募お待ちしています。

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

【プロダクト一覧】 ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Androidクラウド型会計ソフト『MFクラウド会計』クラウド型請求書管理ソフト『MFクラウド請求書』クラウド型給与計算ソフト『MFクラウド給与』経費精算システム『MFクラウド経費』消込ソフト・システム『MFクラウド消込』マイナンバー対応『MFクラウドマイナンバー』創業支援トータルサービス『MFクラウド創業支援サービス』お金に関する正しい知識やお得な情報を発信するウェブメディア『マネトク!』