Project

General

Profile

Feature #15574

Prohibit to pass a block on super() implicitly

Added by ko1 (Koichi Sasada) 7 months ago. Updated 6 months ago.

Status:
Open
Priority:
Normal
Target version:
-
[ruby-core:91332]

Description

As described in [Feature #15554], super() (not super) pass the given block.

class C
  def foo
    p block_given?
  end
end

class C1 < C
  def foo
    super   #=> true
    super() #=> true
  end
end

C1.new.foo{}

super (without parameters) passes all passed parameters so it is no surprise to pass given block.

However, super() (with parameters. In this case, it passes 0 parameters) also pass given block implicitly.

I'm not sure who use this behavior, but I think it is simple to prohibit such implicit block passing.

History

Updated by Eregon (Benoit Daloze) 7 months ago

I would think this is tricky for compatibility (there is definitely code relying on it), but let's see.

Updated by marcandre (Marc-Andre Lafortune) 6 months ago

I agree with Eregon that it would be a compatibility nightmare.

Moreover I rather like this quirk.

Is there an actual use case for thinking about removing it (besides it being quirky)?

I would bet that there are way more methods calling super with the block intact than the reverse. I will frequently prepend a method that intercepts a parameter, for example, deals with it and call super with the rest:

def foo(*args, **options, extra_opt: nil)
  puts "extra!" if extra_opt
  super(*args, **options)
end

I don't recall passing a different (or no) block to super, but my memory isn't very good ;-)

In short, I'm against this proposal.

Updated by ko1 (Koichi Sasada) 6 months ago

marcandre (Marc-Andre Lafortune) wrote:

Is there an actual use case for thinking about removing it (besides it being quirky)?

I assume this spec is because of historical reason. Block parameter is introduced ruby-1.1b9_01, but maybe Matz wanted to pass block to initialize method (it's my speculation).

Exceptional rule is difficult to learn, so if we have no reason to keep compatibility, I want to remove this exceptional rule.

I would bet that there are way more methods calling super with the block intact than the reverse. I will frequently prepend a method that intercepts a parameter, for example, deals with it and call super with the rest:

def foo(*args, **options, extra_opt: nil)
  puts "extra!" if extra_opt
  super(*args, **options)
end

I want to clarify this spec is desired or not.

Why don't you pass a block parameter explicitly?
Because you know the spec and intentional, or simply forget to pass it (and working it with this spec fortunately)?

Updated by duerst (Martin Dürst) 6 months ago

ko1 (Koichi Sasada) wrote:

Why don't you pass a block parameter explicitly?
Because you know the spec and intentional, or simply forget to pass it (and working it with this spec fortunately)?

Until quite recently, using an explicit block parameter was (considered to be?) less efficient than an implicit block parameter.

Updated by sawa (Tsuyoshi Sawada) 6 months ago

duerst (Martin Dürst) wrote:

ko1 (Koichi Sasada) wrote:

Why don't you pass a block parameter explicitly?
Because you know the spec and intentional, or simply forget to pass it (and working it with this spec fortunately)?

Until quite recently, using an explicit block parameter was (considered to be?) less efficient than an implicit block parameter.

But we can explicitly pass yield, can't we?

I am for this proposal.

Updated by sos4nt (Stefan Schüßler) 6 months ago

I prefer the current behavior. Passing along a block is much more common than removing a block.

Let's say I have a class which yields self during initialization:

class A
  def initialize(foo)
    # ...
    yield self if block_given?
  end
end

I can easily subclass the above and add an extra argument via:

class B < A
  def initialize(foo, bar = nil)
    # ...
    super(foo)
  end
end

I don't even have to know whether A takes a block or not, Ruby takes care of it.

With the proposed change however, it becomes my responsibility. Almost every time I use super(...) I have to remember passing the block. The above code would become:

class B < A
  def initialize(foo, bar = nil, &block)
    # ...
    super(foo, &block)
  end
end

Adding an explicit &block (and therefore creating a Proc object) for the sake of passing it along is something I'd like to avoid.


BTW, if you really have to remove all arguments including the block argument, it's merely:

super(&nil)

That looks just fine to me.

Updated by sawa (Tsuyoshi Sawada) 6 months ago

sos4nt (Stefan Schüßler) wrote:

With the proposed change however, it becomes my responsibility. Almost every time I use super(...) I have to remember passing the block. The above code would become:

class B < A
  def initialize(foo, bar = nil, &block)
    # ...
    super(foo, &block)
  end
end

In such case, i.e., when you don't want to take the responsibility of managing the arguments and blocks, I think you should use super, not super(foo, &block). Using super() is a way to explicitly take care of the arguments, and it is counter-intuitive and inconsistent to let only the block be passed automatically.

Updated by sos4nt (Stefan Schüßler) 6 months ago

sawa (Tsuyoshi Sawada) wrote:

In such case, i.e., when you don't want to take the responsibility of managing the arguments and blocks, I think you should use super, not super(foo, &block)

Calling super (without parentheses) would pass two arguments (foo and bar) to the super method which only takes one argument, resulting in an ArgumentError.

#9

Updated by sawa (Tsuyoshi Sawada) 6 months ago

sos4nt (Stefan Schüßler) wrote:

Calling super (without parentheses) would pass two arguments (foo and bar) to the super method which only takes one argument, resulting in an ArgumentError.

In such case, you do need explicit control of which arguments to pass. And it is inconsistent that you want to take care of the arguments but not the block.

Updated by Eregon (Benoit Daloze) 6 months ago

duerst (Martin Dürst) wrote:

Until quite recently, using an explicit block parameter was (considered to be?) less efficient than an implicit block parameter.

I think this is a relevant point and might be part of the reason many codebases don't use an explicit block parameter to call super().

sawa (Tsuyoshi Sawada) wrote:

But we can explicitly pass yield, can't we?

That creates another block as overhead, if you mean super(a, b) { yield }, and that doesn't forward block arguments correctly, so it's not a solution.

In any case, I think before taking any final decision on this proposal we need to test multiple gems to estimate the compatibility impact.
Fixing stdlib for this change might also illustrate how much has to be fixed, although stdlib is very little Ruby code compared to all gems.

Updated by sos4nt (Stefan Schüßler) 6 months ago

sawa (Tsuyoshi Sawada) wrote:

[...] it is inconsistent that you want to take care of the arguments but not the block.

When overriding a method, I try to preserve its signature. Therefore, I usually use super and I sometimes use super(...) with optional arguments as shown above. But having to unset a block via super(&nil) happens once in a blue moon.

My point is: it might be inconsistent, but it is a good inconsistency. It does what you almost always want.

And since super is a keyword, I think it can get away with it.

Also available in: Atom PDF