初参加&初登壇の RubyKaigi Takeout 2021 で Ruby のマクロの話をしてきたよレポート

書こう書こうと思って気がついたら1ヶ月立っていましたこんにちは。
(去年を除いて)今回が RubyKaigi 初参加&初登壇ということでいろいろと書き残しておこうと思います。
ちなみに今週末に以下のようなイベントで雑に話すので興味がある方がぜひぜひ参加してみてくださいー。

ちなみに『Ruby のマクロ』という主語がクソデカなんですがあくまでも『この登壇では』という前提になります。

Use Macro all the time ~ マクロを使いまくろ ~

動画

www.youtube.com

補足

  • RubyVM::AST はバージョンごとに非互換
    • Ruby 2.6 -> 2.7 で :ARRAY -> :LIST に命令が変わったのがでかい
    • 現時点で存在してるバージョンはすべて対応している
  • Ruby 3.1dev では RubyVM::AST::Node から元のソースコードを取得する機能が入った
    • ただし『配列形式の AST』は依然として復元することができない
    • それはそう
src = <<~EOS
if hoge
  puts hoge + foo
end
EOS

# keep_script_lines を true にすると
node = RubyVM::AbstractSyntaxTree.parse(src, keep_script_lines: true)

# #source メソッドでコードを取得できるようになる
puts node.source
# => if hoge
#      puts hoge + foo
#    end

Ruby でマクロを実装しようと思った経緯

元々は1年ぐらい前に『ブロック内のコードをテキストで取得したい』ということをやりたくて最初はこんな感じで iseq からブロックが定義されているファイルや位置情報を取得してきて実際にそのファイルからソースコードを取得する実装を書いていました。
最初はこれを使っていろいろと遊んでたんですがファイルを読み込む必要がある都合上どうしてもパフォーマンスが気になっていました。
そこで RubyVM::AbstractSyntaxTree.of を利用してブロックから AST を取得し、それを元にして Ruby のコードを復元しよう!としたのが rensei-gem をつくりはじめたきっかけですね。
しばらくは『AST から Ruby のコードに変換する』を目的ととして rensei-gem をつくっていたんですが『あれ、AST を好きに変えたら Ruby のコードの意味を変えれるのでは…?』『これってマクロでは…?』などと考えるようになりました。
元々は Rust のようなマクロが Ruby にもほしいな〜〜〜と思っていて Rust のマクロを調べていた事もあったんですが Rust のマクロがまさに AST レベルでコードを変更するような機能になりますね。
とはいえ構想だけで1年ぐらい何もしていなかったんですが今回の RubyKaigi をきっかけに実際に実装してみた、って感じですね。

RubyKaigi への登壇のきっかけ

最初は RubyKaigi に登壇する事は考えてなかったんですが知り合いが CFP を出すという事で『じゃあ、わたしも出してみるか〜〜〜』というのが直接的なきっかけになります。
Ruby のマクロの話も前々からどこかではしたいと思っていたのでそれで CFP を出してみたって感じです。
なので CFP を書き始めたのは結構ギリギリでしたね。
具体的には CFP の締切が6月末で、6月23日から CFP を書きはじめました。
時系列的には以下のような感じですね。

全体的な時系列

  • 2020/07〜08頃:この頃から AST -> Ruby に変換する実装のたたき台を書き始める
  • 2020/09〜:各 AST の種類から Ruby のコードに変換する処理をチマチマ書き始める
    • 1日1対応する、みたいなことをしていた記憶
  • 2020/12/01:Ruby Advent Calendar で AST -> Ruby のコードに変換する記事を書く
  • 2021/06/23:知り合いに触発されて RubyKaigi の CFP を書き始める
  • 2021/06/30:RubyKaigi の CFP 締切
    • 知り合いに翻訳のチェックなど行ってもらいだいぶ助かった…
  • 2021/07/01:CFP の結果はまだだったがすぐに作業を開始
    • Scrapbox を用意
    • Scrapbox に TODO リストや作業ログ、たたき台などを残していった
    • 最初は rensei-gem の残り作業を拾うところから
      • 無限に Rensei のバグ修正をしていた
  • 2021/07/05:Scrapbox に日報を書くように開始
    • 今回これをやったのがかなりよかった
    • ほぼほぼ毎日書いてた
  • 2021/07/14:CFP が Accept されたと連絡をもらう
    • 録画かリアルタイムか選択できて無限に悩み始める
      • 内容が膨れることはわかっていたので録画の方がよさそうだなあ、と思いつつリアルタイムの方がギリギリまでスライドをかけるので…
      • 結局リアルタイムを選択
  • 2021/07/14:ここから『Ruby のマクロとは…』を考え始める
    • Rensei のバグ修正に終わりが見えずにこのままではやばいと気づき始める
      • Rensei のバグ修正は最悪直ってなくても大丈夫ではあった
    • この時にはじめて kenma-gem(Ruby のマクロを実装する gem)のたたき台を書き始める
  • 2021/07/23:スライドのアウトラインを考え始める
  • 2021/07/28:kenma-gem の仮実装が固まる
    • 一段落したのでここからしばらく虚無が続く…
    • 仮実装を知り合いに見てもらいつつ意見をもらい始める
  • 2021/08/01:作業再開
    • kenma(仮実装)を gem 化した
  • 2021/08/11:実際に kenma-gem でオレオレマクロを定義しつつ課題を探し始める
  • 2021/08/23:スライドを書き始め、ここで Scrapbox のログが途絶える
    • ここからしばらく(プライベートで)超絶つらい期間が続く…
    • 何も記憶がない
    • ひたすらスライドを書いては知り合いに壁打ちをお願いしてたような…
  • 2021/09/11:登壇当日
    • なのか無事に終わらせた…

スライドに関して

スライド作成はいつも Reveal.js を使っていたんですがもうちょっとリッチなスライドをつくりたくて今回 slidev を使用してスライドをつくりました。
最初は slidev でいい感じに作れるやろ〜〜〜と軽く考えてたんですが実際はスライドを作っていた期間のうち半分は slidev と戦っていましたね…。なかなかいい感じにアニメーションとか作ることができなくて…。
スライド自体もなるべく前提知識がなくてもわかるように構成しようとすると無限にスライドが膨らんでしまいどこを削るのかかなり悩んでいました。
RubyVM::AST の説明は最後まで入れるべきかどうか悩んでいましたねえ…。今思うと入れなくてもよかったかなあ…。
本当は rensei-gem や kenma-gem の実装の話とかもしたかったんですが今回はあくあまでも『Ruby のマクロとは』という店にフォーカスを当てた内容にしました。
実装に関してはまたどこかで機会があれば話してみたいですねえ。

所感

はい、ということで RubyKaigi 初参加で初登壇する実績を解除しました。
今回はじめての参加だったので当日まで本当にどうなってしまうのかめちゃくちゃ不安だったんですがなんとか(登壇も時間どおり)無事に終わらせることができたのでそこが一番よかったです。
登壇自体は事前にかなり練習していたのでそれはやってよかったです。
内容的には結構ウケてたんですかね?あんまり実感がなくてよくわからないんですが終わった後に特に話を聞くこともないのでまあそんな感じだったのでしょう。
そういう意味では手応え的なのは全然なかったですねえ。
それはそれとして個人的には言いたかった事は全部言えたと思うので満足感はかなりあります。
マクロの機能自体も当初から想像していた形にほぼほぼできたと思うのでそういう意味だと達成感がありすぎて逆にやる気が虚無になっていますね…。
登壇でも言ってたんですが、ファイル単位でガッツリ書き換える、っていう運用はまだまだ先だろうけど局所的にマクロを使うのはそんなにハードルが高くないとは思っているのでそのあたりは実用的にしてみたいなあ、という気持ち。
あとマジで Scrapbox にログを残すようにする運用はよかったので今後も利用していきたい。
最後に運営の方々、マクロ実装・登壇の壁打ちに付き合ってくれた方々ありがとうございましたー。

反省

  • スライドが大盛りになって時間がかなりカツカツになってしまった…
    • かなり削ったけどもうちょい削れる事ができそうだったなあ…
    • 結果的には時間ちょうどで終わらせる事ができたけどゆっくり喋れなくて翻訳の方に申し訳ない…
  • Rensei が対応している Ruby のバージョンを表記しておくべきだった
    • Ruby 2.6 ~ 3.1dev まで対応している
  • スライドの作成がギリギリになっていた
    • 練習するたびにスライドのミスを見つけてつらい
    • 最後の1週間はずーっとスライドの練習に付き合ってもらっていた。めっちゃ感謝
  • 実際の配信画面を見てなかったので実際にどう映っていたのかなんもわからねえ

RubyKaigi で気になった発表

  • TypeProf for IDE: Enrich Dev-Experience without Annotations
    • Ruby の TypeProf を利用して静的片付けのような開発体験を実現する話
    • 静的片付けがほしいのではなくて開発体験がほしいわかる
      • 静的片付けがほしいのではなくて開発体験をよくしたい〜〜〜
    • シグネチャ表示がほしい…ほしい…
    • VSCode 高機能なので Vim でもほしい
    • 静的コードチェックは Rubocop 使っているけどもうちょい別の Lint を使いたい
      • 言語的な意味でのエラーチェックを行ってほしい
    • gem のコード開発は基本的に自身では呼ばれない事があるのでそのあたりどう TypeProf で解析するようになるのかは気になる
      • テストコードを含めて TypeProf で解析するようにしたい
    • RBS ファイルをシュッと記述する機能は Vim にもほしい
      • こういうのは TypeProf 側で提供されている機能になるんかな
      • なるべく VSCode に依存しないような機能として提供されてほしい
    • プロジェクトごとに TypeProf の LSP を起動するのはどうするのがいいんじゃろうか
  • The Art of Execution Control for Ruby's Debugger
    • Ruby のデバッガの debug-gem の話
    • コンソール画面で入力コマンドが見えなくて sob
    • VSCode で変数の中身が見れるの便利だなあ
      • Vim でもやりたい
    • break do: 便利そう
    • 今すぐ欲しいリモートデバッグ
    • 高機能なので使いこなせるかが不安
      • 使いこなしたい
    • step back すごい
    • デバッグだけ VSCode を使うのでもいい気がしてきた
    • TracePoint を使って実装されている、と
    • エディタとの連携部分がもうちょい知りたかった
  • Parallel testing with Ractors: putting CPUs to work
    • Ractor を使った並列テストを行うためのテストフレームワークの話
    • どう並列テストを行うかの解説がわかりやすい
    • Ractor をある程度理解してないとコードをその場で追うのは大変かも
    • テストフレームワークの実装や処理の流れが理解しやすかった
    • デモのテスト実行中にいろいろと操作できるのがすごい
    • Ractor の制限とかバグ無限につらそう
  • Demystifying DSLs for better analysis and understanding
    • RubyDSL を静的解析の話
    • Ruby のコードを例にして DLS を解説しているので『DSL is なに』みたいな人が見てみると理解が捗りそう
      • メタプロの話もでてくるのでそのあたりも学習につながる
    • DSL から RBI を生成し、 RBI で静的チェックなどを行う
    • RubyVim の親和性をもっとよくしたい
  • Parsing Ruby
    • Ruby の歴史を追っていく話
    • 知らない話がでてきて面白い
    • 今までいろんなことをやってきたんだなあ
      • parser-gem は 2.0 の時代につくられたのか
  • include/prepend in refinements should be prohibited
    • Refinements つらい話
    • 話の中であったハマりポイントは実際自分も何度かハマっているので代替機能はほしいんだが〜〜〜
    • include / prepend は多用しているので将来的に deprecated になるのは怖いがある程度しょうがなさそう
    • 代替機能があるならまあがんばって移行していくしかないなあ
  • Beware the Dead End!!
    • end がなかった時などのシンタックスエラーがどこでエラーになっているのかを検知する gem の話
    • 内容がコミカルで面白い
    • gem-dead_end 今すぐ使いたい!!
  • Graphical Terminal User Interface of Ruby 3.1
    • 今年も沢登りの話、と思いきや reline にダイアログ機能が実装されて irb で自動補完が追加された話
    • 風景はあいかわらずだけど内容はガチ
    • 実際に最新の irb を使うとかなりインパクトがでかい
  • Dive into Encoding

2021/10/14 今回の気になった bugs.ruby のチケット

今週は feature チケットに関するトリアージガイドラインの提案などがありました。

[Bug #18250] Anonymous variables seem to break Ractor.make_shareable

  • 以下のようなコードを実行すると Ractor.make_shareableTypeError が発生するというバグ報告
def foo(*); ->{ super };end
# error: `make_shareable': wrong argument type false (expected Symbol) (TypeError)
Ractor.make_shareable(foo) # expected Symbol
  • TypeError ではなくて Ractor::IsolationError が発生するのが期待する挙動らしい
  • ちなみに Ractor.make_shareable は Ractor 間でオブジェクトを使用するために引数を不変にするメソッド
  • #freeze とは違ってネストしてオブジェクトを freeze する
# freeze は配列の中身までは freeze しない
ary = [{ a: 1, }, { b: 2 }]
ary.freeze
pp ary.frozen?      # => true
pp ary[0].frozen?   # => false
pp ary[1].frozen?   # => false

# freeze は配列の中身までは freeze しない
ary = [{ a: 1, }, { b: 2 }]
Ractor.make_shareable(ary)
pp ary.frozen?      # => true
pp ary[0].frozen?   # => true
pp ary[1].frozen?   # => true

[Bug #18246] send does not work for unary ! operator when operator isn't a literal symbol

  • ! 単項演算子send で呼び出すとエラーになるというバグ報告
# これは + 二項演算子を呼ぶ
1.send(:+, 2)    # => 3
1.send(:"+", 2)  # => 3

# これは - 単項演算子を呼ぶ
1.send(:-@)   #=> -1
1.send(:"-@") #=> -1

# これは ! 単項演算子を呼び出してほしい
false.send(:!@)   #=> true
# error: undefined method `!@' for false:FalseClass (NoMethodError)
false.send(:"!@")
  • これは期待する挙動で ! 単項演算子を呼び出す場合は ! メソッド名で呼び出す必要がある
    • false.send(:"!") # => true
  • 逆に false.send(:!@) がなぜ動くのかと言うと :!@:! になるので false.send(:!) と同じ意味になるから
p :!@     # => :!
p :"!@"   # => :"!@"
  • 知らんかった

[Misc #18248] Add Feature Triaging Guide

Rubyは2019年6月にバグトリアージガイドを追加しましたが、それ以降はそれを使ってトラッカーのオープンバグを1400以上から300程度まで減らしました。Rubyでは現在、課題トラッカーに1200以上のオープンな機能リクエストがあります。ざっと見たところ、これらの多くはすでに実装されており、多くは望まれていないと思われます。私はRubyに機能トリアージガイドを追加し、機能リクエストのトリアージを開始したいと考えています。オープンな機能リクエストは、まだ実装されていない機能のうち、Rubyコアチームが実装を希望したり、パッチを検討したりするものを目標としています。そうすることで、潜在的Rubyへの貢献者が、自分ができる貢献の可能性を簡単に知ることができるようになります。

Ruby の debug-gem をつかってみた

RubyKaigi で発表があった debug-gem を使ってみたので覚書。
思ったよりもいろんな機能があった。

インストール

$ gem install debug

でインストールするか Gemfile に以下を追加して debug-gem を導入します。

gem "debug", ">= 1.0.0"

最新版を使いたい場合はこう。

gem "debug", git: "git@github.com:ruby/debug.git", branch: "master"

簡単な使い方

いくつか使い方はあるが今回は binding.break を使ってデバッグしてみる。

コード

  • 処理を止めたい場所に binding.break を仕込んでおく
    • ここで処理が止まる
require 'debug'

a = 1
b = 2
# binding.irb のように binding.break で処理が止まる
binding.break
c = 3
d = 4
binding.break
p [a, b, c, d]

デバッグ実行

  • 通常通り ruby コマンドでコードを実行すれば OK
  • binding.break したところで処理が止まる
$ bundle exec ruby sample.rb
[1, 9] in sample.rb
     1| require 'debug'
     2|
     3| a = 1
     4| b = 2
= >   5| binding.break
     6| c = 3
     7| d = 4
     8| binding.break
     9| p [a, b, c, d]
= >#0   <main> at sample.rb:5
  • ここで info locals を入力して確定するとローカル変数一覧が表示されたりする
$ bundle exec ruby sample.rb 
[1, 9] in sample.rb
     1| require 'debug'
     2| 
     3| a = 1
     4| b = 2
= >   5| binding.break
     6| c = 3
     7| d = 4
     8| binding.break
     9| p [a, b, c, d]
= >#0   <main> at sample.rb:5
(rdbg) info locals    # command
%self = main
a = 1
b = 2
c = nil
d = nil
(rdbg) 
  • 他にも nextstep でステップ実行したり p {Ruby の式} で式の実行結果を表示したりいろいろな機能がある
  • 詳しくは README を参照

所感

  • binding.irb みたいに binding.break で処理を止めてデバッグするのでとっつきやすいっちゃとっつきやすい
  • 対話時にコマンドを入力すると右側にコメントで注釈が出てくるのが便利
  • debug-gem 便利そう!と思いつつ byebug も使いこなせてないので debug-gem 使いこなせるかどうか…
  • プロセスへのアタッチはまだ試してないので試しておきたい
    • 既存の Rails へのプロセスへアタッチして中身をデバッグしてみたりとかしたいなあ…
  • RubyKaigi の登壇だとあんまり VSCode との連携の話が出てなかったのでこのあたりが気になる
    • DAP をサポートしているみたいなので VimDAP プラグインといい感じに接続できないかなあ…
  • Ruby 3.1 がいろいろと楽しみになってきた
  • 次は step back の記事を書くぞ…

2021/10/09 今回の気になった bugs.ruby のチケット

今週は Immutable モジュールを追加して不変なオブジェクトを明示化しよう、というチケットがありました。

[Feature #18035] Introduce general model/semantic for immutable by default.

module Immutable
  def new(...)
    super.freeze
  end
end

class MyImmutableObject
  extend Immutable

  def initialize(x)
    @x = x
  end

  def freeze
    return self if frozen?

    @x.freeze

    super
  end
end

o = MyImmutableObject.new([1, 2, 3])
puts o.frozen?
# => true

[Bug #1823] Conversion to float not working for object with to_f method

  • Thread#join で引数を Float に変換しているが #to_f が呼ばれていないというバグ報告
    • Ruby 3.0 以降から再現するようになった
class Something
  def to_f
    0.1
  end
end

# error: `join': can't convert Something into Float (TypeError)
Thread.new{ }.join(Something.new)
  • このバグは既に修正済み

[Bug #18180] opt_newarray_min/max instructions ignore refined methods

  • 以下のようなケースで Refinements が正しく反映されていないというバグ報告
module M
  refine Array do
    def min; :min; end
    def max; :max; end
  end
end

using M

# これは Refinements が適用される
pp [1, 2, 3].min    # => :min

# これは Refinements が適用されない
pp [1+0, 2, 3].min  # => 1

[Feature #6118Feature #6118] Hash#keys_of(values), returns related keys of given values

  • Hash から指定した要素のキーを複数個取ってくる Hash#keys_of メソッドの提案
data = { a: 1, b: 2, c: 3, d: 1 }
# これは1つだけを返す
p data.key(1)   # => :a

# keys_of の場合は見つかったキーをすべて返す
p data.keys_of(1)   # => [:a, :d]

vim-lsp で TypeProf の LSP と接続してみた

RubyKaigi からだいぶ時間が立ってしまっていますが、やってみました。

前提

下準備

TypeProf 側の準備

ここ2. TypeProfを手元で動かす まで進めておきます。

Vim 側の設定

vim-lsp の設定は以下のようになります。

let s:port = "38587"

function! s:lsp_setup() abort
    call lsp#register_server({
   \    'name': 'typeprof',
   \    "tcp": { server_info-> "localhost:" . s:port },
   \    'allowlist': ['ruby']
   \})
endfunction

augroup lsp_install
    autocmd User lsp_setup call s:lsp_setup()
augroup END

TypeProf は TCP サーバで立ち上がるのでそのサーバと接続する設定をしています。
Vim 側では自動で LSP のサーバは立ち上がらないので手動で TypeProf の LSP のサーバを立ち上げておく必要があります。

実際に試してみる

1. サンプル環境のダウンロード

ここからサンプル環境をダウンロードしてきます。
ダウンロードしてきたら Gemfile の typeprofpath に自分で clone してきた TypeProf のパスを指定しておきます。

source "https://rubygems.org"

gem "typeprof", path: %q{TypeProf のパス}

これで bundle install しておけば OK です。

$ bundle exec typeprof --version
typeprof 0.15.2

2. TypeProf の LSP を起動

1. のサンプル環境で typeprof のサーバを立ち上げます。

$ bundle exec typeprof  -I./ --lsp --port=38587
{"host":"127.0.0.1","port":38587,"pid":1143259}

-I でロードパスを指定します。
今回は直下のファイルを参照したいので ./ を渡しています。
詳しくはこのあたりを読んでください。
port は起動毎に変わるようになっているんですが、今回は Vim 側で接続先を決め打ちしているので port 番号を固定しています。
これで vim-lsp と接続する準備が整いました、

3. sample-vim-lsp-typeprof/main.rbVim で開く

sample-vim-lsp-typeprof/main.rbVim で開きます。
Vim 上で :LspStatus を実行して typeprof: running が表示されれば OK です。
これで LSP の機能が使える状態になっています。

LSP の機能を使ってみる

コード補完する

インサートモードで <C-x><C-o> すると LSP のコード補完が動作します。
今回は自動補完ではなくて手動補完で試しています。

f:id:osyo-manga:20211003214409g:plain

定義ジャンプ

:LspDefinition で定義ジャンプします。

f:id:osyo-manga:20211003215102g:plain

参照箇所の列挙

:LspReferences で参照箇所の一覧を表示します。

f:id:osyo-manga:20211003214213g:plain

所感

と、言う感じで簡単に試してみました。
TypeProf の LSP の立て方は要課題なんですがそれっぽく動作していますね。
正直、Ruby でコード補完はそこまで困ってないんですが、コードリーディング時の定義ジャンプとかは利用していきたいと思っているのでそのあたりが RBS なしでも安定して動作するようになれば真面目に導入していきたい気持ちが高まってきますね。
定義ジャンプであればパフォーマンスもそこまで気にならないと思いますし。
ちなみに自動補完は deoplete.nvim + deoplete-vim-lsp を使っているんですがサーバに変なリクエスト?と飛ばしてるっぽくて TypeProf 側のサーバで死にまくっていましたね…。

2021/10/02 今回の気になった bugs.ruby のチケット

今週は YJIT の導入チケットがつくられました。

[Feature #18229] Proposal to merge YJIT

[Feature #18159] Integrate functionality of dead_end gem into Ruby

2021/09/23 今回の気になった bugs.ruby のチケット

今週は min / max / minmax で比較する値とその結果の要素を返すメソッドの提案などがありました。

[Bug #18187] Float#clamp() returns ArgumentError (comparison of Float with 1 failed)

  • Float::NAN.clamp(0, 100) すると ArgumentError が発生するがこれは期待する挙動ではない、というチケット
  • 例えば以下のように Ruby#clamp を実装した場合は Float::NAN を返す
Float.define_method(:clamp2) { |min, max| self < min ? min : self > max ? max : self }

p 8.0.clamp2(10, 100)
# => 10
p 80.0.clamp2(10, 100)
# => 80.0
p 800.0.clamp2(10, 100)
# => 100
p Float::NAN.clamp2(10, 100)
# => NaN

[Bug #18188] -1 ** 0 is 1 not -1

  • -1 ** 01 を期待するが実際には -1 が返ってくる、というバグ報告
  • が、Ruby-1 ** 0-(1 ** 0) としてパースするのでこれは期待する挙動になっている
    • (-1) ** 01 が返ってくる
# これは -(1 ** 0)
p(-1 ** 0)
# => -1

p(-(1 ** 0))
# => -1

p((-1) ** 0)
# => 1

[Feature #18181] Introduce Enumerable#min_with_value, max_with_value, and minmax_with_value

  • Enumerable#min_with_value, max_with_value, and minmax_with_value を追加する提案
  • これは比較するための値とその要素を一緒に返すためのメソッドになる
# 要素と比較する size の値の両方を結果として返す
%w(abcde fg hijk).min_with_value { |e| e.size } # => ['fg', 2]
%w(abcde fg hijk).max_with_value { |e| e.size } # => ['abcde', 5]
%w(abcde fg hijk).minmax_with_value { |e| e.size } # => [['fg', 2], ['abcde', 5]]
  • 引数を渡すとその個数分の結果も返ってくる
%w(abcde fg hijk).min_with_value(2) { |e| e.size } # => [['fg', 2], ['hijk', 4]]
%w(abcde fg hijk).max_with_value(2) { |e| e.size } # => [['abcde', 5], ['hijk', 4]]
  • 現状だと以下のように書く必要がある
    • のでもっと簡素に書きたいというのが要求みたい?
p %w(abcde fg hijk).map { |e| [e.size, e] }.min_by(&:first)
# => [2, "fg"]
%w(abcde fg hi jkl mn).min_by_with_elements(&:size) # => [2, ["fg", "hi", "mn"]]
  • ただし #min_by は複数の要素があっても単一の要素を返すのでそっちに合わせるなら1つがよさそう?
p %w(abcde fg hi jkl mn).min_by(&:size)
# => "fg"

[Feature #18179] Add Math methods to Numeric

  • Math.sqrt などのクラスメソッドの x.sqrt のように Numericインスタンスメソッドにするチケット
  • オブジェクト指向的にはインスタンスメソッドの方が自然な気がする
  • このチケットとは関係ないんですがこういう Hoge.foo(x) みたいなメソッドをシュッと x.foo みたいに呼び出せる機能がほしい
    • パイプライン演算子みたいなのがほしいって言うわけではなくても Hoge モジュールをインスタンスメソッドとして組み込むような機能

[Bug #18170] Exception#inspect should not include newlines

  • 次のようにエラー内容に改行が含まれている場合、標準出力でも改行されてしまう
p StandardError.new("foo\nbar")
#=>
# #<StandardError: foo
# bar>
  • これを #<StandardError: "foo\nbar"> を返すようにするのはどうか、という提案
  • did_you_meanerror_highlight で複数行のエラーを表示する際に出力が奇妙になるらしい
class Foo
  def initialize
    @exception = begin; exampl; rescue Exception; $!; end
  end

  def example
  end
end

p Foo.new
#=>
# #<Foo:0x00007f15aeb4ba48 @exception=#<NameError: undefined local variable or method `exampl' for #<Foo:0x00007f15aeb4ba48 ...>
#
#     @exception = begin; exampl; rescue Exception; $!; end
#                         ^^^^^^
# Did you mean?  example>>

[Bug #17048] Calling initialize_copy on live modules leads to crashes

  • 以下のコードで Ruby がクラッシュするというバグ報告
loop do
  m = Module.new do
    prepend Module.new
    def hello
    end
  end

  klass = Class.new { include m }
  m.send(:initialize_copy, Module.new)
  GC.start

  klass.new.hello rescue nil
end
  • Module#initialize_copy を呼び出すとクラッシュする可能性があるらしい
  • initialize_copy はレシーバを引数のオブジェクトの内容で置き換えるメソッド
  • 最新版では Module#initialize_copy を呼び出すと TypeError が発生するように修正されている
module A
end

# error: `initialize_copy': already initialized module (TypeError)
A.send(:initialize_copy, Module.new) # fine, no one inherits from A