Bug #10684
closedBlock arity changes through Enumerable methods
Description
Blocks traveling through methods in Enumerable have their arity changed before reaching #each. Example:
class MyEnumerator
include Enumerable
def initialize(ary)
@ary = ary
end
def each(&block)
puts block.arity
@ary.each(&block)
end
end
my_enum = MyEnumerator.new([1,2,3])
my_enum.each{|x| x} # outputs 1
my_enum.detect{|x| x} # outputs -1
This is surprising behavior. I would expect the output to be 1 in both cases since the blocks appear identical to me as a programmer.
Updated by jakesower (Jake Sower) about 9 years ago
Blocks traveling through methods in Enumerable have their arity changed before reaching #each. Example:
class MyEnumerable
include Enumerable
def initialize(ary)
@ary = ary
end
def each(&block)
puts block.arity
@ary.each(&block)
end
end
my_enum = MyEnumerable.new([1,2,3])
my_enum.each{|x| x} # outputs 1
my_enum.detect{|x| x} # outputs -1
This is surprising behavior. I would expect the output to be 1 in both cases since the blocks appear identical to me as a programmer.
Updated by austin (Austin Ziegler) about 9 years ago
It’s not that surprising to me.
While Enumerable#detect
is written in C, in Ruby I might implement it as:
module Enumerable
def detect2
return enum_for(:detect2) unless block_given?
v = each { |x| break x if yield x }
v == self ? nil : v
end
end
In this case, the C code is instructive:
static VALUE
enum_find(int argc, VALUE *argv, VALUE obj)
{
NODE *memo;
VALUE if_none;
rb_scan_args(argc, argv, "01", &if_none);
RETURN_ENUMERATOR(obj, argc, argv);
memo = NEW_MEMO(Qundef, 0, 0);
rb_block_call(obj, id_each, 0, 0, find_i, (VALUE)memo);
if (memo->u3.cnt) {
return memo->u1.value;
}
if (!NIL_P(if_none)) {
return rb_funcall(if_none, id_call, 0, 0);
}
return Qnil;
}
In practice, it's doing it more like:
module Enumerable
def detect3
return enum_for(:detect3) unless block_given?
inject(nil) { |m, x|
v = yield x
m ||= v if v
m
}
end
end
Updated by jeremyevans0 (Jeremy Evans) over 3 years ago
- Status changed from Open to Rejected