# Zuby 下書き ずびしいず ## チケット下書き --- ## background - hogefuga - reachabilty判定の導入によって改善されるもの - `("b".."aa")` のような単純な辞書順比較によって正しく評価されていなかったrangeが正しく評価されるようになる - #5607 `("b".."aa").to_a => []` になる問題もこれで解決できる - `Range#include?` - `String#upto` で全列挙していた実装の代わりに本methodを使うことで判定が高速に行えるようになる - https://bugs.ruby-lang.org/issues/5798 - ` ("b".."aa").include?("z")`, `("b"..).include?("aa")` などの辞書順比較を用いていたことによるバグが直る - https://bugs.ruby-lang.org/issues/5607 ## algorithm string contain with alunm(s) - "a" is reachable to "a", "z", "aa", "abc" - "b" is reachable to "z", "aa", "abc", unreachable to "a" - "0" is reachable to "9", 10", "10298" - "0" is unreachable to ":", "a", "0a" - "aa00" is reachalbe to "ab00", "abcasdfv84" - "." is reachable to "10982" - 書くのめんどくせえ string with no alunm - '\0' <= a[-1] <= 'z' - "//" is reachable to "/10" - "//" is unreachable to "/010" - "//" is unreachable to "/?" - "@@" is reachable to "@AA" - "@@" is unreachable to "@_" - "``" is reachable to "`aa" - "``" is unreachable to "`~" - a =~ /[^\x2f\x40\x60\x7f]\x7f*[\x7b-\x7f]$/ - "{\x7f{" is reachable to "{\x7f~" - "{\x7f{" is reachable to "{\x00!" - "{\x7f{" is reachable to "|\x0010" - a =~ /[\x2f\x40\x60]\x7f*[\x7b-\x7f]$/ - "@@\x7f{" is reachable to "@A\x00\x00" - "@@\x7f{" is unreachable to "@A\x00\x01" - "@@\x7f{" is reachable to "@AA\x00\x00" - a =~ /^\x7f*[\x7b-\x7f]$/ - "\x7f{" is reachable to "\x01\x00\x00" - "\x7f{" is reachable to "\x01\x0010" --- ## 発表スライド https://docs.google.com/presentation/d/156xZN9OrN6NPyof3E_1K-oOeCf0J-Hh8vtcuvoxjcto/edit?usp=sharing ## CRuby の Rangeの改善 - 上に関連して,Rubyについて,rangeの始端もしくは終端にUTF-8文字が含まれると非自明な挙動をする - `("a".."あ").to_a #=> ["a", ... , "zzy", "zzz"] ` - str_upto_each はsuccで到達できないものについても処理を書いているので把握済み? - https://github.com/ruby/ruby/blob/master/string.c#L4457 - 本当は始端のsucc* が終端にならないものはそもそもrangeとして受け付けないでほしい(個人的に) - これは例えばFloatのrangeを受け入れている以上現実的ではなさそうという気持ちになった - `[*"z".."aa"] #=> []` - `[*"a"..."z"] + [*"z".."aa"] == [*"a".."aa"]` になるのが自然だが、`[*"z".."aa"]` が空になるためこれは一致しない - https://bugs.ruby-lang.org/issues/5607 - 関連: https://bugs.ruby-lang.org/issues/2323 - これは [string.c#L4444](https://github.com/ruby/ruby/blob/master/string.c#L4444) の判定が誤っていて、例えば`(c..a)`のようなものにイテレーションを走らせないようにした結果,このように到達できるものもはじいてしまっている - 「『a が辞書順で b より大きい』ならば『a は b に unreachable である』」は偽 - issue でも言われている通り、ASCII に絞った対応なら現実的&需要があるのでは? - `("aa"..).include?("b") #=> true` - 上とは逆に unreachable なものを reachable 判定してしまう - これも辞書順比較だけで済ませてしまっているため - 単にbegin/endlessの実装ミスっぽい - rubyのRange#include のstringが特殊なケースを除いてString#uptoを呼び出しているため遅い - Range#includeは始端終端がstringの場合` rb_str_include_range_p@string.c` を呼び出す - https://github.com/ruby/ruby/blob/master/string.c#L4518 - 始端終端がasciiかつ1文字の場合のみ単純な比較を行い,それ以外は`rb_str_upto_each` を呼び出している - `(0..1).include?(0.5) #=> true` - #cover?は連続値,#include?は離散値という原則が存在するにも関わらず数値のみこういう扱いになっている - 仕様らしい - https://docs.ruby-lang.org/ja/2.7.0/method/Range/i/cover=3f.html - String#succ のアルゴリズム - `str_succ` - 末尾から見ていき alnum があればそこを次の文字にする - 次の文字にするのは `enc_succ_alnum_char` を使う - 次の文字にできない場合は「繰り上がり」のように振る舞う - 一個前の alnum を次の文字にする - 先頭まで来てしまったら文字を前方向に伸ばす - 一個も alnum がなかったら、単に文字として似たことをやる - `enc_succ_char` を使う - `enc_succ_alnum_char` - alnum 判定は、Unicode Character Properties に基づくと思われる - 非 alnum を1回までなら超えられる(この回数は `max_gaps` によって定められる) - だめなら巻き戻す(巻き戻しは非 alnum を超えられない) - `enc_succ_char` - エンコーディングによって場合分けがされている - バイト長に関する処理? - `enc_pred_char` - だいたい `enc_succ_char` の逆 - `enum neighbor_char` - `NEIGHBOR_NOT_CHAR` - 「次(前)が文字として無効だよ」みたいな意味 - `NEIGHBOR_FOUND` - 「次(前)が文字として有効だよ」みたいな意味 - `NEIGHBOR_WRAPPED` - `enc_succ_alnum_char` だと「次が文字として無効だったから巻き戻せるところまで巻き戻したよ」みたいな意味 - `enc_succ_char` だとバイト長が変わったみたいな意味? - メモ - String#uptoがString#succに依存しない例 ``` (".".."13").to_a => [".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"] (".".."=").to_a => [".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "="] ``` - https://docs.ruby-lang.org/ja/latest/method/String/i/upto.html ``` 話の流れを切ってしまい申し訳ありません. CRubyのRange#include? について,現状の実装だとRangeの始終端がStringの場合,[rb_str_include_range_p](https://github.com/ruby/ruby/blob/master/string.c#L4518)が呼び出されるんですが,これは始端終端がasciiかつ1文字の場合以外については`rb_str_upto_each`を呼び出しているため重く例えば`("a".."zzzzz").include?("zzzzz")` は(手元の環境で)2秒程度かかっています. 上のように(他の例として"1".."100","あ".."を"など)始端がsuccを辿って終端に到達可能なrangeについては文字列の長さ及び文字列の各文字の比較によって`rb_str_upto_each`で1個ずつ辿ること無く高速にincludeかを判定できるため,そのような実装をRange#include?に追加することを考えているのですがこのアイデアについてご意見いただければと思います. 現状気になっているいる点は ・始端と終端が`rb_str_upto_each`を使わず比較可能かは「文字種」が始端終端で一致しているかを確認すればよいと考えていて,具体的には始端終端が { 数字を表すStringの場合比較可能(例: "1".."10")/ そうでない場合ascii文字列ならば比較可能(例: "a".."aaaaa") / そうでない場合比較が難しいもしくは不可能なのでuptoを呼び出す必要がある} で可能と考えているのですが, ・("a".."あ")のような終端が始端からunreachableなrangeについては今までと同様に`rb_str_upto_each` を呼び出す他ないように思えるんですが,そもそもこのようなrangeは許容・想定されているのでしょうか ( str_upto の[ここ](https://github.com/ruby/ruby/blob/master/string.c#L4457 )を見る限り想定はされているのかなという所感ではあります(これにより例えば("a".."あ").each は "zzz"で止まります), blameや過去のチケットを軽く眺めてみたのですが関連する議論は見つけられませんでした) ``` ``` $ time ruby -e '("a".."zzzzz").include?("zzzzz")' ruby -e '("a".."zzzzz").include?("zzzzz")' 2.19s user 0.10s system 89% cpu 2.543 total ``` - 到達可能性判定アルゴリズム? ```ruby= ASCII = /[[:ascii:]]/ ALNUM = /[[:alnum:]]/ ALPHA = /[[:alpha:]]/ UPPER = /[[:upper:]]/ LOWER = /[[:lower:]]/ DIGIT = /[[:digit:]]/ def ascii_str_reachable?(a, b) raise unless a.ascii_only? raise unless b.ascii_only? return true if a == "" && b == "" return false if a == "" || b == "" # succ によって文字数が減ることはないので弾ける return false if a.size > b.size # assert a.size <= b.size # 参考: string.c str_succ L4241-L4264 # 後ろから見ていき、任意回の succ によって変更されうる始点を見つける # 非 alnum を超えるには {digit / alpha} が一致している必要がある ...(1) last_alnum_index = nil neighbor_is_alnum = true i = a.size - 1 while i >= 0 if !neighbor_is_alnum && last_alnum_index case a[i] when DIGIT break if a[last_alnum_index] =~ ALPHA when ALPHA break if a[last_alnum_index] =~ DIGIT end end if a[i] =~ ALNUM last_alnum_index = i neighbor_is_alnum = true else neighbor_is_alnum = false end i -= 1 end if last_alnum_index # last_alnum_index より以前は変化しないので一致している必要がある for i in 0...last_alnum_index return false if a[i] != b[i] end # 後ろから見て {digit / upper / lower / else} が一致している必要がある # else なら文字自体が一致している必要がある # b の超過分については a[last_alnum_index] のものと一致している必要がある ...(2) # (ルール(1)と(2)によって、任意回の succ をしても last_alnum_index の位置が変わらないことが言える) i = a.size - 1 j = b.size - 1 prepend = false while j >= last_alnum_index case a[i] when DIGIT return false if b[j] !~ DIGIT return false if prepend && b[j] == "0" when UPPER return false if b[j] !~ UPPER when LOWER return false if b[j] !~ LOWER else return false if a[i] != b[j] end if i > last_alnum_index i -= 1 else prepend = true end j -= 1 end # あとは普通の自然数の大小比較と同じ case when a.size < b.size return true when a.size == b.size i = last_alnum_index while i < a.size if a[i] =~ ALNUM case when a[i] > b[i] return false when a[i] < b[i] return true end end i += 1 end return true end else # TODO: alnum を含まない場合 # 参考: string.c str_succ L4265-L4297 # UTF-8の場合は \x7f の次が \x00 になって繰り上がる # (ASCII-8bitの場合 \x7f.succ == \x80) # 十分な回数やると alnum ありにたどり着く # a[-1] を succ していき、 # - '0', 'A', 'a' に突入するパターン # - 突入するまでに b と一致するパターン # - 突入し、繰り上がりが起こるなどして b と一致するパターン # - '\x7f' を超えるパターン # - 超えるまでに b と一致するパターン # - 繰り上がって a[-1] 自身が '0' に突入するパターン # - 突入するまでに b と一致するパターン # - 突入し、繰り上がりが起こるなどして b と一致するパターン # - 繰り上げられた文字が '0', 'A', 'a' に突入するパターン # - 繰り上がって先頭に '\x01' がつき a[-1] が '0' に突入するパターン # - 突入するまでに b と一致するパターン # - 突入し、繰り上がりが起こるなどして b と一致するパターン # [wrap_start, wrap_end) ... a[-1] から到達しうる ['0', '9'), ['A', 'Z'), ['a', 'z'), ['\x80', ) などの区間 # [carry_pos, carry_pos + carry_len) ... a から succ していって alnum を含み始めたとき b のどの部分に含みうるか wrap_start = nil wrap_end = nil carry_pos = nil carry_len = nil ch = a[-1].ord ch += 1 while ch.chr =~ ASCII && ch.chr !~ ALNUM wrap_start = ch.chr if wrap_start =~ ASCII return false if a[...(a.size - 1)] != b[...(a.size - 1)] return true if a.size == b.size && a[-1] <= b[-1] && b[-1] <= wrap_start carry_pos = a.size - 1 carry_len = b.size - carry_pos else return true if a.size == b.size && a[...(a.size - 1)] != b[...(a.size - 1)] && a[-1] <= b[-1] && b[-1] < wrap_start carry_pos = a.size - 2 carry_pos -= 1 while carry_pos >= 0 && (a[carry_pos].ord + 1).chr !~ ASCII if carry_pos >= 0 return false if a[...carry_pos] != b[...carry_pos] if (a[carry_pos].ord + 1).chr !~ ALNUM return false if (a[carry_pos].ord + 1).chr != b[carry_pos] for j in (carry_pos + 1)...(a.size - 1) return false if b[j] != "\0" end ch = "\0".ord ch += 1 while ch.chr !~ ALNUM wrap_start = ch.chr return true if a.size == b.size && b[-1] < wrap_start carry_pos = a.size - 1 carry_len = b.size - carry_pos else wrap_start = (a[carry_pos].ord + 1).chr carry_len = 1 + (b.size - a.size) for j in (carry_pos + carry_len)...b.size return false if b[j] != "\0" end end else return false if b[0] != "\x01" for j in 1...a.size return false if b[j] != "\0" end ch = "\0".ord ch += 1 while ch.chr !~ ALNUM wrap_start = ch.chr return true if a.size + 1 == b.size && b[-1] < wrap_start carry_pos = a.size carry_len = b.size - a.size end end ch = wrap_start.ord ch += 1 while ch.chr =~ ALNUM wrap_end = ch.chr if wrap_start =~ DIGIT return false if carry_len > 1 && b[carry_pos] == wrap_start end for j in carry_pos...(carry_pos + carry_len) return false if b[j] < wrap_start || wrap_end <= b[j] end return true end end ``` - reachabilty判定の導入によって改善されるもの - `("b".."aa")` のような単純な辞書順比較によって正しく評価されていなかったrangeが正しく評価されるようになる - #5607 `("b".."aa").to_a => []` になる問題もこれで解決できる - `Range#include?` - `String#upto` で全列挙していた実装の代わりに本methodを使うことで判定が高速に行えるようになる - ` ("b".."aa").include?("z")`, `("b"..).include?("aa")` などの辞書順比較を用いていたことによるバグが直る - TODO - [ ] C移植 - /string.c, /internal/string.h - [x] `ascii_str_reachable_p` - [x] `rb_str_upto_each` - [x] `rb_str_include_range_p` - /range.c - [x] `range_include_internal` - [ ] テスト - [ ] ベンチマーク - [ ] PR - [ ] 本家の方で相談 - memo - [gdbを使ったrubyのデバッグ](https://techlife.cookpad.com/entry/2015/12/09/163746) - `bool RTEST(VALUE value)` ## mrubyへのendless/beginless rangeの追加 - https://github.com/mruby/mruby/issues/5085 - 進捗状況 - [x] parserの変更 - [x] Range#eachのInteger(FIXNUM)の場合の実装 - [x] Range#eachのStringの場合の実装 - `("a"..).each{|c| p c}` - 本家ではC側で処理していて通常の String#upto に対応する`rb_str_upto`およびこれのメインロジックに相当する`rb_str_upto_each`とは別にendless用に`rb_str_upto_endless_each`を用意している. - mrubyではRuby側で処理していて `String#upto` を使っている - ここにendless range用の処理を組み込む場合どうするか - `String#upto` の第一引数endに `nil` を渡すことを許容する - 本家と挙動が異なってしまう / 元々のuptoインターフェースを破壊してしまう - Ruby側でStringクラスにendless range用のメソッドを新たに用意する - どうやって隠蔽? - String#__sub_replace という前例が存在 - 隠蔽はできてない - each内でStringにパッチをあてるなりしてupto_endlessを生やす / インラインでベタ書きしちゃう - String#upto は拡張として切り分けられているのにまあまあな量の処理を拡張でないところに入れてしまうのは - C側で生やす - Ruby側から使うにはメソッドなりなんなりで登録する必要があり結局隠蔽ができない. - mrubyの他のコードで`__` prefixつきのメソッドをprivateとして実装しているものがあるためこれを踏襲する(使えてはしまうが) - `__sub_replace@string.rb` など - `_`の数は1個だったり2個だったりする(え?) - とりあえずアンスコでの実装を選択,完了 - [x] [1,2,3][0..]の実装(`mrb_range_beg_len()@range.c`) - [x] EXCLUSIVE[0...]への対応(`mrb_range_beg_len()@range.c`) - [x] `Range#to_s`, `Range#inspect` - `nil.to_s` は空文字になるので結果的に `Range#to_s` 改変の必要はない - [x] Range#{include?(member), #min, #===, #cover?} - https://github.com/ruby/ruby/commit/db1bdecb0d925b4668c0735158fce466333848f1 - [test] min - [x] include?, member?, === - `('b'..'d').include?('ba')` の挙動が ruby と mruby で異なる問題がある // TODO - [x] cover? - [x] `Range#{max, last}` - nilではなくてraiseするようにする - https://github.com/ruby/ruby/commit/cae45174190b49ca3c67ca606c5f4b71e3847842 - [x] last: DONE - [x] max: - [x] Range#sizeについて - 本家RubyではFloat::INFINITYを返しているがmrubyはFloat無しの環境もありえる.この場合に何を返すか. - https://github.com/ruby/ruby/commit/342619300ce26f9a134249002270c5d38d5993b3 - とりあえず nil を返すようにして PR 時に相談 - そもそも MRB_WITHOUT_FLOAT 下での動作確認が十分になされていなさそう - 始端終端がNumericじゃない場合はnilらしい - ``('a'..'z').size # => nil` - [x] eachで初期値がFIXNUM::MAXのときどうするか - https://github.com/ruby/ruby/commit/db885d0850653425b2c7896f9e6730c57b030ab1 - そもそもmrubyではfixnumの範囲を超えるとFloatになるのでendless rangeでない場合も壊れる ``` (9223372036854775807..9223372036854775808).each{|e| p e} ``` - 別PRかなあ - [x] Range#to_a - https://github.com/ruby/ruby/commit/c19ecf05b4c728951e1a1e223a40ae6883a4f8e0 - [x] テストの追加 - /test/t/range.rb, /mrbgems/mruby-range-ext/test/range.rb - `Range` - Range#Hashのテストが無かったり,Float skipがあったりなかったりする - /test/t/array.rb - `Array#[]`, `Array#[]=` - /test/t/string.rb - `String#[] with Range` - Ruby では本来 [ここ](https://github.com/ruby/ruby/blob/master/test/ruby/test_string.rb#L119-L123) にテストが書かれるべきだが、忘れられている? - mruby にそもそも `String#[]= with Range` がなかったので生やした - /mrbgems/mruby-string-ext/test/range.rb - Range の要素が String のときのテスト - /mrbgems/mruby-enum-ext/test/enum.rb - `Enumerable#take` など各種メソッド - 変更が増えるので見送り - [ ] Rubyの変更コードとの比較(この部分はいる・いらない選択, 最終確認) - あと、関係ないけどもとのコードにあった冗長な部分(lenの二重代入とか、TODOでメモったやつ) ## 副産物として見つけたバグなど - [x] [BETTER]Range#eachでlenが2重代入されている - 別コミットで対応 - https://github.com/mruby/mruby/pull/5091 - merged - [x] [BETTER]Range#Hashのテストが無い - 書いた - [x] [BETTER]RangeのテストでFloat skipがあったりなかったりする - 多分`MRB_WITHOUT_FLOAT`すると落ちる - 書いた - [ ] [BETTER] RubyのString#[]にendless/beginless rangeのテストが無い (endless mergeされた後) - [x] [BETTER]Range#minのテストミス - https://github.com/mruby/mruby/pull/5089 - merged - [ ] (1)[BUG] mrubyで `('b'..'d').include?('ba') # => true` - [ ] [BUG?] 上に関連して,Rubyについて,rangeの始端もしくは終端にUTF-8文字が含まれると非自明な挙動をする - `("a".."あ").each{|e|p e}` - str_upto_each はsuccで到達できないものについても処理を書いているので把握済み? - https://github.com/ruby/ruby/blob/f0c7a05b4c7657f1cff1698e5560f49248a08bfd/string.c#L4457 - [ ] [BETTER] rubyのRange#include のstringが特殊なケースを除いてString#uptoを呼び出しているため遅い - Range#includeは始端終端がstringの場合` rb_str_include_range_p@string.c` を呼び出す - https://github.com/ruby/ruby/blob/master/string.c#L4518 - 始端終端がasciiかつ1文字の場合のみ単純な比較を行い,それ以外は`rb_str_upto_each` を呼び出している - [ ] [BUG] mrubyで`(9223372036854775807..9223372036854775808).each` が壊れる - [x] [BUG] `WITHOUT_FLOAT` でコンパイルが通らない - https://github.com/mruby/mruby/pull/5090 - merged - [ ] [FEATURE] beginless range ## PR下書き (TITLE) Introduce endless range. (a part of https://github.com/mruby/mruby/issues/5085) We introduced endless range feature, which is proposed in https://github.com/mruby/mruby/issues/5085 and already supported by CRuby. ### Current Behavior ``` > Range.new(1,nil) bad value for range (ArgumentError) ``` ### Behavior after this commit ``` # mruby > Range.new(1,nil) => 1.. > (1...) => 1... > (1..).each {|n| p n} 1 2 3 (snip) > ('a'...).each {|c| p c; break if c>='f'} "a" "b" "c" "d" "e" "f" => nil > "Ruby"[1..] => "uby" > a = [0, 1, 2, 3, 4]; a[1..] = 5 => 5 > a => [0, 5] # CRuby(2.6.6) 2.6.6 :001 > Range.new(1,nil) => 1.. 2.6.6 :002 > (1...) => 1... 2.6.6 :001 > (1..).each {|n| p n} 1 2 3 (snip) 2.6.6 :011 > ('a'...).each {|c| p c; break if c>='f'} "a" "b" "c" "d" "e" "f" => nil 2.6.6 :001 > "Ruby"[1..] => "uby" 2.6.6 :002 > a = [0, 1, 2, 3, 4]; a[1..] = 5 2.6.6 :003 > a => [0, 5] ``` For other changes, please see the diff. ### Things to notice - Range#size returns Float::INFINITY when it is endless. (This behavior is based on CRuby.) Under `MRB_WITHOUT_FLOAT` environment, we implemented to return nil for now. ``` # CRuby(2.6.6) 2.6.6 :015 > (1..).size => Infinity # mruby > (1..).size => Infinity # mruby (built with MRB_WITHOUT_FLOAT flag) > (1..).size => nil ``` - Range#last - According to ISO, Range#last is an alias of Range#end (15.2.14.4.10), which is actually not the case in current Ruby anymore (e.g. Range#last can take 0 or 1 argument, but Range#end does not take arguments.). Since the core of mruby basically needs to follow ISO, those features in CRuby have been implemeted as extension(mruby-range-ext) in current mruby. - In CRuby, Range#last raises a RangeError when it is endless. (while Range#end returns nil). So we implemented this feature to extension(mruby-range-ext)'s Range#last following that implementation. ``` # CRuby(2.6.6) 2.6.6 :002 > (1..).last Traceback (most recent call last): 5: from /home/wataru/ruby/bin/irb:23:in `<main>' 4: from /home/wataru/ruby/bin/irb:23:in `load' 3: from /home/wataru/.rvm/gems/ruby-2.6.6/gems/irb-1.2.4/exe/irb:11:in `<top (required)>' 2: from (irb):2 1: from (irb):2:in `last' RangeError (cannot get the last element of endless range) # mruby (core) > (1..).last => nil # mruby (with mruby-range-ext) > (1..).last cannot get the last element of endless range (RangeError) ``` - __upto_endless - To implement Range#each, we need to prepare upto method of String corresponding to endless range separately from the existing Stinrg#upto (CRuby also takes the same steps). According to the other implementation (like [TODO]) in mruby, we implement Stinrg#__upto_endless for private in use. ------ PR下書きオワリ --------------------------------- ``` > (1..).min => 1 > (1..).max cannot get the maximum of endless range (RangeError) > (1..).to_s => "1.." > (1..).to_a cannot get the last element of endless range (RangeError) > (1..).first => 1 2.6.6 :003 > (1..).min => 1 2.6.6 :004 > (1..).max Traceback (most recent call last): 5: from /home/wataru/ruby/bin/irb:23:in `<main>' 4: from /home/wataru/ruby/bin/irb:23:in `load' 3: from /home/wataru/.rvm/gems/ruby-2.6.6/gems/irb-1.2.4/exe/irb:11:in `<top (required)>' 2: from (irb):4 1: from (irb):4:in `max' RangeError (cannot get the maximum of endless range) 2.6.6 :013 > (1..).to_s => "1.." 2.6.6 :014 > (1..).to_a Traceback (most recent call last): 5: from /home/wataru/ruby/bin/irb:23:in `<main>' 4: from /home/wataru/ruby/bin/irb:23:in `load' 3: from /home/wataru/.rvm/gems/ruby-2.6.6/gems/irb-1.2.4/exe/irb:11:in `<top (required)>' 2: from (irb):14 1: from (irb):14:in `to_a' RangeError (cannot convert endless range to an array) 2.6.6 :001 > (1..).first => 1 ``` ## memo - `MRB_WITHOUT_FLOAT` を定義してコンパイルすることで Float を排除できる - https://github.com/mruby/mruby/pull/3827 - Range#stepはmrubyには未実装 - nil判定 - C: `mrb_nil_p(a)` - Ruby: `a.nil?` - コーディングスタイル - C - else の前は改行 - ``` if (a) { b } else { c } ``` - 1行でない if 文を使うときは波括弧をつける - 必要に合わせて `=` の位置を揃える場合と揃えない場合があるぞい - コメントで `//` は使わない、`/* */` のみ使う - Ruby - 引数があるときのメソッド呼び出しは括弧をつける場合とつけない場合があるぞい ## 案出し - Ruby本体 - priority queue ほしい(三浦が個人的に) - データ構造の名前としては binary heap - 検索しても特に既存の提案は見つからない - set みたいに標準ライブラリであってほしい - Haskell の scan ほしい(三浦が個人的に) - 流石に既出だったし実装済みだった(提案3ヶ月前、最終更新1ヶ月前) - https://bugs.ruby-lang.org/issues/17016 - https://github.com/ruby/ruby/pull/3078 - 名前決めの段階? 若干放置気味 - 適当なFeatureを投げる - [irb](https://github.com/ruby/irb) - コードの読み込みが pry-byebug(https://github.com/deivid-rodriguez/byebug, https://github.com/deivid-rodriguez/pry-byebug) に比べて有意に遅いのでこれを改善したい - 適当に数行のコードを貼り付けるとかなり分かるはず - ~~恐らくsyntax highlightが重い?~~ ruby/relineも含めて見る必要がありそう - チケット切られてるバグ - Endless method definitionにlambdaを食わせると正しく動作しない - https://bugs.ruby-lang.org/issues/17193 - Endless method definitionについて - https://bugs.ruby-lang.org/issues/16746 - https://github.com/ruby/ruby/pull/2996 - https://github.com/ruby/irb/pull/131 を手元で動かした所これで直っているっぽい - [mruby](https://github.com/mruby/mruby) - 組み込み用途のRuby - 言語仕様が本家より小さいので手を付けやすい? - 具体的に何したいかは未定 - Issue - endless / beginless rangeを実装したい - https://github.com/mruby/mruby/issues/5085 - endless-range: ruby-2.6.0-preview2から実装 - https://bugs.ruby-lang.org/issues/12912 - https://github.com/ruby/ruby/commit/7f95eed19e22cb9a4867819355fe4ab99f85fd16 - 周辺コミット - [Make Range#min, max, include?, cover?, and === to support endless range](https://github.com/ruby/ruby/commit/db1bdecb0d925b4668c0735158fce466333848f1) - beginless-range: v2_7_0_preview1から実装 - https://github.com/ruby/ruby/commit/95f7992b89efd35de6b28ac095c4d3477019c583 - mRubyのRangeでnil指定した場合の挙動をrubyに合わせ - Matzさんが"Accept"しているが"priority is low"としている - mrubyよく知らんけど、rubyの該当コードぱくるだけになりそう? - https://github.com/ruby/ruby/blame/master/range.c#L430 - 構文木とかもいじらないといけないかも(`Range.new`だけならnilチェックだけでいいだろうけど、``1..`のような場合) - Rubyとmrubyの実装が似て非なる感じなのでいい感じに移植が大変そう? - コア - https://github.com/mruby/mruby/blob/master/src/range.c - https://github.com/mruby/mruby/blob/master/mrblib/range.rb - 構文解析 - https://github.com/mruby/mruby/blob/master/mrbgems/mruby-compiler/core/parse.y#L2117-L2124 - 拡張? - https://github.com/mruby/mruby/blob/master/mrbgems/mruby-range-ext/src/range.c - https://github.com/mruby/mruby/blob/master/mrbgems/mruby-range-ext/mrblib/range.rb - [rbs](https://github.com/ruby/rbs) - Rubyコードで型シグニチャ(所謂型アノテーション?)が使えるようにするgem - https://www.ruby-lang.org/en/news/2020/09/25/ruby-3-0-0-preview1-released/ - v3.xの一部としてリリースされることになっているらしい - https://tadd.hatenablog.jp/entry/2020/09/10/205812 - 今年GSoCでrbsをやっていた人のレポート - 標準ライブラリはまだまだ型がついてないっぽい - https://docs.google.com/spreadsheets/d/199rRB93I16H0k4TGZS3EGojns2R0W1oCsN8UPJzOpyU/edit#gid=1383307992 - https://github.com/ruby/rbs/blob/master/docs/stdlib.md - 手探る感はない ## 参考リンクなど - https://bugs.ruby-lang.org/ (Ruby本体のissue tracker) - [ruby-jp Slack で雑に Ruby コミッターさんへ質問を投げてみた](https://jinroq.hatenablog.jp/entry/2019/08/08/120704) - [古い本 (今は処理系変わってるらしいけど、あんまよく知らんので...)](http://i.loveruby.net/ja/rhg/book/)