7

I was reading the source code for concurrent-ruby, and came across this line of ruby code.

def initialize(*args, &block)
  super(&nil) # <--- ???
  synchronize { ns_initialize(*args, &block) }
end

Can someone explain to me what it is supposed to do?

sawa
  • 165,429
  • 45
  • 277
  • 381
snw
  • 303
  • 3
  • 8
  • 2
    It is probably calling the super class' constructor, with no block (`&nil` might be an empty block) – Guillaume Jun 03 '18 at 20:50
  • @Guillaume but the superclass does not have initialize method defined – seethrough Jun 03 '18 at 21:27
  • It appears that code is from [here](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/executor/abstract_executor_service.rb#L21) and has to do with the initialization of a thread. See also @PeterO. 's answer [here](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/executor/abstract_executor_service.rb#L21). – Cary Swoveland Jun 03 '18 at 23:20
  • @CarySwoveland you accidentally gave the same link twice. – max pleaner Jun 04 '18 at 00:02
  • @snw please see the edit to my answer – max pleaner Jun 04 '18 at 01:35
  • 1
    Thanks, @max. The second link I gave should be [this](https://stackoverflow.com/questions/7410748/how-does-foonil-behave-differently-than-foonot-a-proc). I believe this is relevant to your answer. – Cary Swoveland Jun 04 '18 at 03:16

1 Answers1

3

You have to start by understanding the & operator as it's used here. See for example:

# The & here converts a block argument to a proc
def a(&blk)
end

# The & here converts the proc to a block
a(&Proc.new { true })

In the proc => block case, it is also capable of turning some objects into procs, for example:

# The symbol :class gets to_proc called here
[1].map(&:class)

Symbol#to_proc produces the same functionality as follows

[1].map(&Proc.new { |x| x.class })

I'm not sure where the official documentation for this is (would welcome a pointer), but from testing it seems that &nil does not actually pass any block to the method at all - it has no effect:

def a
  block_given?
end

a {} # => true
a &:puts # => true
a &nil # => false

Now that that is explained, I can go on to say why it's needed.

If you omit parens with super, all arguments are passed:

class A
  def initialize arg
    puts arg && block_given?
  end
end

class B < A
  def initialize arg
    super
  end
end

B.new(1) {}
# prints "true" - block and arg were both passed to super

If you don't want this to happen, you can manually pass arguments to super. There is an issue with this, which I will get to after:

class A
  def initialize arg1, arg2=nil
    puts arg1 && !arg2
  end
end

class B < A
  def initialize arg1, arg2=nil
    super arg1
  end
end

B.new 1, 2
# prints "true" - arg1 was passed to super but not arg2

The problem is that although you can prevent positional and keyword args from being passed, this approach will not prevent a block being passed along:

class A
  def initialize arg1
    puts arg1 && block_given?
  end
end

class B < A
  def initialize arg1
    super arg1
  end
end

B.new(1) { }
# prints "true" - arg and block were both passed

For whatever reason, it is important here that it not happen, so they use an idiom I have not seen before but appears to get the job done: &nil. It's essentially saying "pass nothing as a block". I guess if you don't do this then blocks are automatically forwarded.

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • and there is no actually a super constructor, at least I could not find it – seethrough Jun 03 '18 at 23:11
  • @seethrough ok i saw that other comment you left as well, but it's under a different username so I didn't realize you were OP. `#initialize` is automatically defined on all classes so there will be a `super` anyway. – max pleaner Jun 03 '18 at 23:50
  • @max_pleaner what is OP? – seethrough Jun 03 '18 at 23:53
  • @seethrough original poster – max pleaner Jun 04 '18 at 00:01
  • I am not. and I mean what I mean, what is the point to send an empty `Proc` or block or whatever if there is no a method decribing what to do with it? And I understand that absence of `initialize` method is ok, object is going to be created. – seethrough Jun 04 '18 at 00:03
  • 2
    There is a 'super' definition by default for initialize - try this `class Foo; def initialize; super; end; end`, it works. As far as why they use `super(&:blk)`, the only thing I can think of is they're trying to distinguish it from `super` (if you don't include parens or arguments, it automatically forwards all args). Although I think this is the same as `super()`, so not really sure what the point is. – max pleaner Jun 04 '18 at 01:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172362/discussion-between-seethrough-and-max-pleaner). – seethrough Jun 04 '18 at 01:15
  • @max_pleaner Make sense, but could you (or someone) possibly point to me where in source code this exceptional case is handled? From the comment by @Guillaume, I guess `&nil` returns an empty block like `{ }`? – snw Jun 04 '18 at 01:45
  • 4
    @snw `&nil` doesn't pass an empty block, it passes no block. I'm not sure you can even say it "returns" anything because it's not valid syntax outside of method definitions or invocations. I'm not sure what you mean "where this exceptional case is handled". The place in the source code is linked in the comments on the question – max pleaner Jun 04 '18 at 01:59