今週の気になった bugs.ruby
1週間だとあんまりたまらないかと思ったんですが思ったよりもたまりました。
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
Feature #16378 Support leading arguments together with ...
(...)
で先頭の引数をキャプチャする提案(...)
は Ruby 2.7 で入った『全引数をデリゲートする機能』- 詳細:https://tmtms.hatenablog.com/entry/201912/ruby27-argument-forwarding
- 以下のように
method_missing
などで利用できるようになる
def method_missing(name, ...) if name.to_s.end_with?('?') self[name] else fallback(name, ...) end end
- 確かにこういうケースで利用できると便利そう
- すでに dev 版でマージ済み
Bug #16948 hash.each(&method(:something)) behavior changed without warning on master
- ruby 2.8.0dev で
hash.each(&method(:something)
が動かなくなったというバグ報告- 2.7.1 では問題ない
def foo(a, b) p [a, b] end # error: in `foo': wrong number of arguments (given 1, expected 2) (ArgumentError) {1 => 2}.each(&method(:foo))
def foo(a, b) p [a, b] end def bar(a, b = 2) p [a, b] end foo_lambda = method(:foo).to_proc bar_lambda = method(:bar).to_proc {a: 1}.each(&foo_lambda) # => [:a, 1] {a: 1}.each(&bar_lambda) # => [[:a, 1], 2]
Bug #16947 private method unexpected behavior
private
メソッドなのにself.hoge
みたいにself.
尽きでメソッドが呼べるのはバグじゃない?という報告- これは Ruby 2.7 から
private
メソッドでもself
尽きで呼び出せるようになったので期待する挙動になる - see: https://rubyreferences.github.io/rubychanges/2.7.html#selfprivate_method
Feature #16946 Add an intersperse
method
- 要素間に任意の値を挿入するメソッドの提案
[1, 2, 3].intersperse(0) #=> [1, 0, 2, 0, 3] 'Hello'.intersperse('-') #=> "H-e-l-l-o"
- どういうケースで利用できるんだろうか
- 既存のメソッドだと以下のような感じらしい
[1, 2, 3].flat_map { |i| [i, 0] }[0...-1] #=> [1, 0, 2, 0, 3] 'Hello'.gsub(/(?<=.)./, '-\0') #=> "H-e-l-l-o" 'Hello'.chars.join('-') #=> "H-e-l-l-o"
Feature #16939 Alias _1 as _ for block numbered params
_
を_1
のエイリアスにする提案_
のほうが短いけど意味がわかりづらいかも…?- 確か
_
は『参照しない引数』みたいな意味で使われている事が多いみたいな話があった気がする
# 第二引数は参照しないので _ を使う hash.map { |k, _| k.to_s }
- 『
_
は既存のコードで多く利用されている』という理由でシュッと閉じられた
Bug #16942 instance_method causes an infinite loop with prepend, include and private
- 特定の mixin を行った状態で
Object.instance_method
を呼ぶと無限ループするバグ
module M def x end end module M2 include M private :x end ::Object.prepend(M2) # infinite loop p Object.instance_method(:x)
module M def x end end module M2 include M private :x prepend Module.new end ::Object.prepend(M2) # infinite loop p Object.instance_method(:x)
- この問題は修正済み
Ruby の or と and の優先順位
Ruby の or
と and
は優先順位がとても低いです。
例えば
x = 1 or 2
は
(x = 1) or 2
と解釈され
foo || bar and hoge
は
(foo || bar) and hoge
と解釈されます。 ちなみに次のようなコードはシンタックスエラーになります。
# syntax error, unexpected `or', expecting ')' f(a or b)
foo || bar && hoge
と foo or bar and hoge
の違い
さて、 ||
と &&
演算子では &&
の方が優先されます。
なので
foo || bar && hoge foo && bar || hoge
は
foo || (bar && hoge) (foo && bar) || hoge
と解釈されます。
一方で or
と and
演算子では『先に書いてある方』が優先されます。
なので
foo or bar and hoge foo and bar or hoge
は
(foo or bar) and hoge (foo and bar) or hoge
となります。
or
and
演算子はあんまり使ったことがないんですがこのあたりの違いは知らなかった。
今週の気になった bugs.ruby のチケット
なんとなく書き溜めるようにしてみました。
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
Feature #16828 Introduce find patterns
- パターンマッチで条件にマッチした一部の要素だけ取得できる提案
case ["a", 1, "b", "c", 2, "d", "e", "f", 3] in [*pre, String => x, String => y, *post] p pre #=> ["a", 1] p x #=> "b" p y #=> "c" p post #=> [2, "d", "e", "f", 3] end
Bug #16852 Refining Enumerable fails with ruby 2.7
- 2.7 系の Refinements のバグ
refine M
したあとにinclude M
を行うといろいろとぶっ壊れるぽい- RSpec 関連で報告があったんですが、最小構成を調べてコメントしました
- 最小構成は以下の通りで RSpec 関係なく再現しました。
module M; end class X include M end module RefinementBug refine M do def refined_method puts "Called #refined_method" end end end class Y # Error if include M include M end using RefinementBug puts "Using Ruby #{RUBY_VERSION}" # error: undefined method `refined_method' for #<X:0x000055d4e22468c0> (NoMethodError) X.new.refined_method
まだ取り込まれてないが修正パッチ作成済み- マージ済み
Bug #16932 defined?
on refined method call returns non-nil even before using
- Refinements で定義したメソッドが
defined?
に反映されていなかったバグ - 修正済み
class A end a = A.new p defined?(a.x) #=> nil (OK) m = Module.new do refine(A) do def x end end end a.x rescue p $! #=> NoMethodError: undefined method `x' p defined?(a.x) #=> "method" (NG) using m p defined?(a.x) #=> "method" (OK)
Feature #14267 Lazy proc allocation introduced in #14045 creates regression
Proc
同士の比較が非互換になって RSpec で困ってる(いた?)らしい- チケット自体はかなり前
# regression.rb def return_proc(&block) block end def return_procs(&block) block.inspect if ENV['INSPECT_BLOCK'] proc_1 = return_proc(&block) proc_2 = return_proc(&block) return proc_1, proc_2 end proc_1, proc_2 = return_procs { } puts RUBY_VERSION puts "Proc equality: #{proc_1 == proc_2}"
$ chruby 2.4 $ ruby regression.rb 2.4.2 Proc equality: true $ chruby 2.5 $ ruby regression.rb 2.5.0 Proc equality: false
- 2.5 当時のチケット
Feature #16928 Array#include_all? & Array#include_any?
Array#include_all?
とArray#include_any?
の追加提案
# Array#include_all?(other_array) [1, 2, 3, 4].include_all?([2, 4]) # returns true [1, 2, 3, 4].include_all?([2, 4, 5]) # returns false [1, 2, 3, 4].include_all?([]) # returns true [1, 2, 3, 4].include_all?(nil) # returns false # Array#include_any?(other_array) [1, 2, 3, 4].include_any?([2, 4, 5]) # returns true [1, 2, 3, 4].include_any?([6, 7]) # returns false [1, 2, 3, 4].include_any?([]) # returns true [1, 2, 3, 4].include_any?(nil) # returns false
- どういうときに便利なんだろうか
- 名前から期待する挙動がわかりづらい
- 既存のコードでもっといい書き方ないかな
Feature #16924 instance_eval equivalent for RubyVM::InstructionSequence
- iseq を任意のコンテキスト(レシーバ)で評価したいっていう提案
# ___path にかかれている Ruby のコードを ___dsl_object をレシーバにして実行したい ___dsl_object.instance_eval(File.read(___path),___path,0)
これを iseq
経由で実行するようにしたい
# ___path の中身を iseq に変換 iseq = RubyVM::InstructionSequence.compile_file(___path) # その iseq を ___dsl_object をレシーバにして評価する iseq.eval_in_instance(___dsl_object)
Bug #16919 IRB でマジックコメントの : の後に任意の文字を入力するとエラーが発生する
- irb 上で
#coding:u
と入力して確定するとirb
がクラッシュする - そもそも確定しなくても入力中にクラッシュする
#coding:utf-8
を入力中等- これは
irb
が入力毎に Ripper でパースしている為`
Ruby で循環参照してる配列で deep_dup するとどうなるか
deep_dup
の話が Twitter で出たので試してみました。
ActiveSupport の #deep_dup
をつかった場合
ActiveSupport に #deep_dup
の実装があるのでそれで試してみました。
require "active_support" require "active_support/core_ext/object/deep_dup.rb" a = [1, 2, 3] a[3] = a # error: in `map': stack level too deep (SystemStackError) p a.deep_dup
と、いうことで SystemStackError
で無事死亡。
Marshal.dump
を使った場合
るりま の Object#clone
のドキュメントに書いてある Marshal.dump
をつかった例を試してみました。
a = [1, 2, 3] a[3] = a a2 = Marshal.load(Marshal.dump(a)) p a2 # => [1, 2, 3, [...]] p a2[3] # => [1, 2, 3, [...]] p a2[3][3] # => [1, 2, 3, [...]] a2[2] = "homu" p a2 # => [1, 2, "homu", [...]] p a2[3] # => [1, 2, "homu", [...]] p a2[3][3] # => [1, 2, "homu", [...]]
雰囲気うごいてそう。
ThinkPad X270 の液晶パネルを交換した話
完成! pic.twitter.com/kO9JYhhkZR
— バンビちゃん@実際コロナじゃなくてもぼっち (@pink_bangbi) 2020年5月3日
何か手頃なラップトップがないかなーと中古品を漁っていたら ThinkPad X270 が安く手に入ったのでついでにはじめてラップトップで液晶パネルを変えてみました。
既存の構成だと液晶は 1366×768 なんですがこれだとややつらいんですよね…。
なので今回は 1920×1080 の FHD の液晶パネルに交換しました。
実際やってみるとそれ自体は結構簡単だったのでこういうのがさくっと交換できる ThinkPad すげー。
購入した液晶パネル
今回は Amazon で液晶パネルを購入しました。
レビューでも X270 で動作したと書かれていたので基本的にはこれで問題ないと思います。
ただ、コネクタの位置が『右側』と『中央』の2種類あり、X270 は『右側』にコネクタがあるので購入する場合はその点だけ注意が必要になります。
その他購入品
シリコンパワー ノートPC用メモリ DDR4-2133(PC4-17000) 16GB×1枚 260pin 1.2V CL15 永久保証 SP016GBSFU213B02
- 発売日: 2017/03/30
- メディア: Personal Computers
X270 だと 16GB のメモリに対応しており 1枚だけ指すことができるのでこれも購入。
デュアルチャンネル?なにそれおいしいの
最近はメモリがオンボードになっていて増設できないラップトップも増えているんですが、X270 あたりだとこのあたりは簡単に増設できるので便利ですね。
Transcend SSD M.2 2242 512GB SATA III 6Gb/s 3D TLC NAND DDR3 DRAMキャッシュ搭載 5年保証 TS512GMTS430S
- 発売日: 2019/05/23
- メディア: Personal Computers
あとストレージも増設。
X270 だと(購入時の構成にもよるんですが)SATA SSD と WWANカードスロットに M.2 2242 SSD の2つにストレージを追加することができます。
今回はもともと付いていた SATA SSD とは別に WWANカードスロットに M.2 2242 SSD を増設しました。
ただ、最近の SATA SSD は高速なので IO 速度的な意味だと M.2 2242 とはほとんど変わらないですね…。
デュアルストレージにする以上の意味合いはなかったのでそのうち SATA SSD も別に購入しそう。
交換作業
1. 液晶パネルの交換
液晶パネルのフレームは爪で引っかかってるだけなので隙間を開けてカードなどで広げて行けば割と簡単に外れます。
フレームを取ると液晶パネルはそのまま取り外す事ができるので前に倒します。
左下にコネクタがあるので引っ張らない用に注意。
コネクタ部分は金具で固定されているので、金具を上に引っ張り上げてコネクタを後ろにスライドさせるようにすれば外す事ができます。
ネットで液晶パネルの交換記事を見てるとこのコネクタを外す作業で戸惑ってる人が結構いたので youtube の交換動画を見て見るといいと思います。
あとは交換した液晶パネルを元の位置に戻してフレームをはめ込んで完成! ちなみに全行程でフレームをはめる作業が一番時間かかりました…。
2. メモリとストレージの交換・増設
次はメモリとストレージの増設。
中身の構成を変える場合は裏蓋を全部引っぺがす必要があります。
ネジは緩めてもフレームからは外れないようになっているので便利です。
メモリは金具を外してそのまま交換。
WWANカードスロットは真ん中の左側の方に空きスペースがあるのでそこに追加。
3. 完成
あとは裏蓋を締めて完成! 特に問題なくシュッと起動しました。
解像度もちゃんと 1920×1080 になっててよかったよかた
所感
と、いうい感じで ThinkPad の液晶パネルを交換してみました。
思ったよりもトラブルなくて交換できたのでよかったよかった。
こういうのがさくっと交換できるのが ThinkPad の強みですねー。
参照
Ruby の右代入とエンドレスメソッド定義文を組み合わせるといろいろとつらいことがわかった
さてさて、先日 Ruby の開発版に右代入とエンドレスメソッド定義構文が入りました。
この構文については以下を参照してください。
この2つの機能を組み合わせて使うといろいろとつらいことがわかってきたのでちょっとまとめてみました
def hoge(value) = value => @value
アクセッサ的に引数をそのままインスタンス変数に代入するコードはよく記述すると思います。
それを右代入とエンドレスメソッド定義で書くと
def hoge(value) = value => @value
のように記述する事ができます。
もうこの時点でよくわからん!!みたいなユーザがいると思うんですがまだ意図としてはわかりやすいと思います。
しかし、これは意図した動作を行いませんでした。
def hoge(value) = value => @value # 42 が @value に代入されてほしい p hoge 42 # しかし実際には @value にはなぜか :hoge が代入されている p @value # => :hoge
これはなぜかというと def =
よりも =>
の方が優先順位が低く (def hoge(value) = value) => @value
と解釈されている(と予想)からです。
なので (def hoge(value) = value)
の結果である :hoge
が @value
へと代入されています。
ただし、この挙動はすでに修正されており最新版では以下のように意図する動作となります。
def hoge(value) = value => @value # 42 が @value に代入される p hoge 42 p @value # => 42
となります。
以降は以下の状態で動作確認しています。
p RUBY_VERSION # => "2.8.0" p RUBY_DESCRIPTION # => "ruby 2.8.0dev (2020-04-13T13:57:10Z master c28e230ab5) [x86_64-linux]"
private def hoge(value) = value => @value
private def hoge(value) = value => @value
のように書くこともできます。
これは hoge
メソッドを private
メソッドとして定義したいという意図になります。
これも先程の修正コミットよりも前はシンタックスエラーになっていたんですが現状では動作するようになっています。
class X # メソッドを定義しつつ private メソッドにする private def hoge(value) = value => @value def foo hoge(42) p @value # => 42 end end p X.new.foo
def hoge(value) = value => @value => method_name
意図としては def hoge
の結果を method_name
に代入したい…という感じなんですがもうよくわかりませんね…。
これは以下のような動作になります。
def hoge(value) = value => @value => method_name hoge 42 p method_name # => :hoge p @value # => 42
意図する動作にはなっています、が…。
private def hoge(value) = value => @value => @value2
もう何がしたいのかよくわかりませんね…。
意図としては value
を @value
と @value2
の2つの変数に代入したいってことですが…。
これは実行時エラーになります。
# error: `private': {:hoge=>nil} is not a symbol nor a string (TypeError) private def hoge(value) = value => @value => @value2
これは
private((def hoge(value) = value => @value) => @value2)
と解釈されて :hoge => nil
を private
メソッドの引数として渡しているから…ですかね。
もうよくわからない。
所感
とにかく =>
と def =
の優先順位がつらいって感じがしますね。
個人的には =>
も def =
も =
ぐらい優先順位が低いとわかりやすいのかあ、とは思うんですが =>
が Hash の定義として使えるのでいろいろとつらそう…。
このあたりを解決するには =>
の記号を変えるしかなさそうなのかなあ…。
ちなみに知り合いは def hoge(value) = @value = value
みたいに =
が連なってるのもつらいと言っていたので def =
も記号としてはつらそう。
うーん、左から右に流れるようにかけるので書いてて気持ちよくはあるんですが上で書いたように凝った書き方をすると意図しない動作になりそうなのできびしそうですねえ…。
その他
42 => result1 => result2
42 => result1 => result2 p result1 # => 42 p result2 # => 42
[1, 2] => result1, result2
[1, 2] => result1, result2 p result1 # => 1 p result2 # => 2
if 式で =>
を使う
# OK if result = value end # Error if value => result end
42 => a = b => c
これはシンタックスエラーになります。
# syntax error, unexpected '=', expecting end-of-input 42 => a = b => c
メソッドにネストした =>
を渡す
def hoge(h) p h end value = 42 # OK : Hash 渡しになる hoge :key => value # => {:key=>42} # NG : syntax error, unexpected =>, expecting end-of-input hoge 42 => value => value
参照
Ruby 2.7 でも 1行 in を利用して右代入を行う
さてさて、先日 Ruby の開発版に右代入を行う =>
演算子が追加されました。
これは 左辺値
を 右辺の変数
へ代入するための演算子になります。
1 + 2 => result result # => 3
Ruby 2.7 でパターンマッチ構文が実装された
Ruby 2.7 ではパターンマッチ構文が実験的に新しく追加されました。
この構文では次のように in
句で特定の値をキャプチャする事ができます。
homu = { name: "homu", age: 14 } case homu in { name:, age: ..20 => age } p name # => "homu" p age # => 14 end
こんな感じで Hash の各要素に対してマッチするかどうかの判定とその値を任意の変数にキャプチャする事ができます。
パターンマッチの 1行 in 構文
先程のパターンマッチでは case ~ in ~ end
という構文だったのですが次のようにして 1行で expr in pattern
を書くこともできます。
homu = { name: "homu", age: 14 } # expr in pattern と1行で書くことができる # マッチしなかったら NoMatchingPatternError になる homu in { name:, age: ..20 => age } p name # => "homu" p age # => 14
こんな感じで in
が1つしかない場合は1行で簡潔に記述する事ができます。
1行 in で右代入を行う
さて、先程は in
の右辺は { name:, age: ..20 => age }
のような複雑なパターンだったんですが、実は右辺には『変数だけ』を定義することができます
homu = { name: "homu", age: 14 } # in の右辺に左辺の値がそのまま代入される homu in result p result # => {:name=>"homu", :age=>14}
つまり in
を利用すると
1 + 2 in result p result # => 3
と記述する事ができます。
これって右代入ですよね?
もちろんメソッドチェーンされた値も代入できます。
(1..10).to_a.shuffle.select { _1.even? }.map { _1 * 2 }.map { _1.to_s } in result p result # => ["20", "12", "16", "8", "4"]
完全に右代入ですね!!!
補足
このネタは 2月18日に行われた Ginza.rb の LT で発表しました。 気になる人は以下のスライドを読んでみるといいと思います。