今週の気になった bugs.ruby

ちょっと遅れましたが貯めてはいました。
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。

[Feature #15973] Let Kernel#lambda always return a lambda

  • lambda(&proc {}).lambda? の戻り値が false になるので true にしよう、という提案
  • Ruby 2.7 現在では以下のような挙動になっている
# lambda にブロックを渡す
# OK: true が返る
p lambda {}.lambda?
# => true

# lambda に lambda を渡す
# OK: true が返る
p lambda(&lambda {}).lambda?
# => true

# lambda に proc を渡す
# NG: false が返る
p lambda(&proc {}).lambda?
# => false
  • 議論は長いんですが結果的には以下のように対応することになったみたい
  • この対応はすでに ruby-dev にて実装済
# ruby-dev での挙動

# no warning
lambda {}

# warning: lambda without a literal block is deprecated; use the proc without lambda instead
lambda(&lambda {})

# warning: lambda without a literal block is deprecated; use the proc without lambda instead
lambda(&proc {})

# warning: lambda without a literal block is deprecated; use the proc without lambda instead
lambda(&method(:puts))

[Feature #12901] Anonymous functions without scope lookup overhead

  • Proc などを定義する際に『外部のスコープを参照しないこと』を明示化することでオーバーヘッドをなくしパフォーマンスが向上させるチケット
    • チケット自体は3年前につくられた
# scope: false をキーワード引数で渡すことで『キャプチャしないこと』を明示化
Proc.new(scope: false) {|var| puts var }

# これは以下のようにメソッドを定義したときと同じ意味
def anon(var)
  puts var
end


# 動作例
var = "hello"
Proc.new(scope: false) { puts var }.call
# => NameError: undefined local variable or method `var' for main:Object
  • 内容がヘビーなのでなかなか追いきれてない…
    • bindingself が絡んでくると最適化するのがむずかしい、みたいな議論がされてるぽい?
    • self をキャプチャしない場合は puts some_expression が動かないよね?みたいなことも言われてる
  • Guild/Ractor には外部スコープを参照しないようにする Proc#isolate というメソッドがあるらしい?
  • レシーバをキャプチャしない場合は UnboundMethod に近いのでは?というコメントも
# こういう構文だとどうか
plus = def->(a) = self + a
plus.bind_call(1, 2) #=> 3
plus_1 = plus.bind(1)
plus_1.call(11) #=> 12

[Feature #6869] Do not treat _ parameter exceptionally

  • def hoge(a, a) end みたいに同名の仮引数を定義すると duplicated argument name とエラーになる
  • しかし def hoge(_, _) end のように _ を仮引数としてつかった場合はエラーにならない
  • _ が特別な挙動になるのはやめましょう、とう提案
# OK
def hoge(_, _) end

# NG: duplicated argument name
def hoge(a, a) end
  • チケット自体は 8年前につくられていて最近コメントされていたので読みました
  • _ が特別な意味を持つのがもにょるのはわかるんですがどうなんでしょうね

[Feature #16954] A new mode Warning[:deprecated] = :error for 2.7

  • Warning[:deprecated] = :errordeprecated な警告をエラーにするか警告にするかを制御できるようにする提案
  • 挙動としては以下のような感じ
    1. Warning[:deprecated] = :error, to make the warning into an error which produces a full backtrace (and stops the execution).
    2. Warning[:deprecated] = :debug, to make the warning print a full backtrace (and continues the execution).
  • このあたりがユーザ側で制御できるのはいいのではなかろうか?
  • ちなみにコメントに書いてあったんですが、以下のようにすると Ruby だけで実装することもできるらしい
require 'warning' # warning gem
Warning.process do |message|
  if message =~ /: warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated)\n\z/
    if false # :error
      raise message
    else # :debug
      $stderr.puts message
      $stderr.puts caller
    end
  else
    $stderr.puts message
  end
end

def a(a, b: 1); end
a(b: 2) # keyword to positional

def a(a=1, b: 1); end
a({b: 1, 'a'=>1}) # split positional
a(b: 1, 'a'=>1) # split keyword

def a(b: 1); end
a({b: 1}) # positional to keyword
  • こういう感じでハックできるのはいろいろと捗りそう