今週の気になった bugs.ruby

書き溜めてはいたんですが、ブログに公開するのを忘れてました。
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。

[Feature #16986] Anonymous Struct literal

  • Struct.new(:a, :b).new(1, 2)${ a: 1, b: 2 } のようなリテラルで定義できるようにするチケット
s = ${a: 1, b: 2, c: 3}
s.a  # => 1
s.b  # => 2
s.c  # => 3
  • いまはそんなに Struct は使わないけどこういう記法があるとガンガン使うと思う
  • 例えばこんな感じに雑にダックタイピング呼び出しするメソッドに値を渡す場合とか
def print(user)
  pp "#{user.id} #{user.name}"
end

name = "homu"
age = 14
# Struct を経由してメソッド呼び出しされるようにする
print(${ name: name, age: age })
  • Struct だと obj.value だけじゃなくて obj[:value] みたいに添え字アクセスもできるので Hash の代わりとして使用できそう
    • Hash と違い存在しないキーにアクセスするとエラーになるのは便利そう
# Hash の場合は typo してても気づきづらい
user = { name: "homu", age: 14 }
# no error
user[:nmae]

# Struct だと存在しないキーにアクセスするとエラーになる
user = Struct.new(:name, :age).new("homu", 14)
# error
user[:nmae]
  • ${} だとブロックと差別化できるのでよい
    • p { a: 1, b: 2 } とは書けないが p ${ a: 1, b: 2 } とはかける
  • リテラルじゃなくて {a: 1, b: 2}.to_struct みたいな変換メソッドがある方が便利そう?
  • Hash リテラルと比較して以下の部分が気になる
  • label: expr みたいな定義のみ許可されていて ${**h} みたいなのは許可されていない
# Symbol でない値をキーにできるか(Symbol だけ?)
${ "a" => 1 }
${ 1 => 1 }

# 変数をキーにできるか
key = :a
${ key => 1 }

# `**` で Hash が展開できるのか
hash = { a: 1, b: 2 }
${ c: 3, **hash }

[Feature #13067] TrueClass,FalseClass to provide === to match truthy/falsy values.

  • TrueClass#=== FalseClass#=== を定義する提案
  • true ==== objfalse === obj したときに objtruthyfalsy か判定する
    • nil false だったら falsy、それ以外なら truthy
  • ary.grep(true) みたいなことができる
  • 以下の case ~ when だと挙動が変わってしまう
def normalize_hsts_options(options)
  # options が nil の場合 case false にマッチしてしまう
  case options
  when false
    # ...
  when nil, true
    # ...
  else
    # ...
  end
end
  • ary.grep(true)ary.grep(false)ary.select(&:itself)ary.reject(&:itself) で置き換えられる
  • 互換性の面からクローズされた

[Feature #16985] Improve pp for Hash and String

  • ppHashString の出力をよくしようとするチケット
pp({hello: 'My name is "Marc-André"'})
# 現状
# => {:hello=>"My name is \"Marc-André\""}
# 提案
# => {hello: 'My name is "Marc-André"'}
  • 普通に便利そう

[Feature #17004] Provide a way for methods to omit their return value

  • 任意のメソッドが戻り値を受け取るか受け取らないかを判定するメソッドの追加
    • RubyVM.return_value_is_used? というメソッドを追加
    • 戻り値を受け取る場合は true を返し、そうでない場合は false を返す
  • こんな感じで判定する事ができる
def hoge
  if RubyVM.return_value_is_used?
    pp "戻り値を受け取る"
  else
    pp "戻り値を受け取らない"
  end
end

hoge          # "戻り値を受け取らない"
value = hoge  # "戻り値を受け取る"
Array hoge    # "戻り値を受け取る"
hoge.nil?     # "戻り値を受け取る"

# 最後に呼び出したやつも?
hoge          # "戻り値を受け取る"
  • これを利用すると次のように『戻り値を受け取らない場合は無駄な処理を省く』事ができる
class Hash
  def refresh(key)
    # 引数を受け取る場合のみ result を設定する
    if RubyVM.return_value_is_used?
      result = self[key]
    end
    self[key] = nil
    result
  end
end

homu = { name: "homu", age: 14 }
homu.refresh(:name)
p homu
# => {:name=>nil, :age=>14}

age = homu.refresh(:age)
pp homu  # => {:name=>nil, :age=>nil}
pp age   # => 14
class User < ActiveRecord::Base
  def update_name(name)
    update!(name: name)

    # reload した値を返す
    reload if RubyVM.return_value_is_used?
  end
end
  • ただし、次のように戻り値になる場合は『戻り値を受け取る』ことになるので注意
def hoge
  if RubyVM.return_value_is_used?
    pp "戻り値を受け取る"
  else
    pp "戻り値を受け取らない"
  end
end

def foo
  hoge
end
foo    # "戻り値を受け取る"

def bar
  hoge
  nil
end
bar    # "戻り値を受け取らない"
  • 便利そうっちゃ便利そうだけどメソッドごとに RubyVM.return_value_is_used? で処理を分岐するのはめっちゃきつそう
    • 実際には極端に重くなるようなメソッドぐらいで使いそうな気がするけど…どうだろう
    • 別のアプローチはないかな…