简体   繁体   中英

Is it possible to declare a method with block as default value?

I want to write a method which takes a block and if no block given it should use a default block. So I want to have something like this:

def say_hello(name, &block = ->(name) { puts "Hi, #{name}" })
  # do something
end 

But when I'm trying to do so I'm getting the syntax error.

I know I can deal with my problem using block_given? . But I am interested in first approach. Am I missing something or this is just not possible?

You cannot declare a default block in the method definition, however you can use a little trick to use a custom block if none is given.

def say_hello(name)
  block = block_given? ? Proc.new : ->(name) { puts "Hi, #{name}" }
  block.call(name)
end

# This example uses a custom block
say_hello('weppos') { |name| puts "Hello, #{name}!" }
# => Hello, weppos!

# This example fallbacks to the default
say_hello('weppos')
# => Hi, weppos!

Let me explain it a little bit. Let's start from a more readable version.

def say_hello(name, &block)
  block = block ? block : ->(name) { puts "Hi, #{name}" }
  block.call(name)
end

You define the method to accept a block, then you check if block is defined. If not, you assign a custom block. Finally, you execute the block.

Let's enhance it a little bit. You can use block_given? to check if a block is passed

def say_hello(name, &block)
  block = block_given? ? block : ->(name) { puts "Hi, #{name}" }
  block.call(name)
end

This also allows you to skip the declaration of the block ( &block ) in the method definition.

def say_hello(name)
  if block_given?
    yield name
  else
    # This is rendundant, but it's for clarity
    block = ->(name) { puts "Hi, #{name}" }
    block.call(name)
  end
end

But, at this point, you can also use the Proc.new to assign the block to a variable.

def say_hello(name)
  block = block_given? ? Proc.new : ->(name) { puts "Hi, #{name}" }
  block.call(name)
end

As a final word, I'm trying to understand when this approach would make sense. In most cases, you can probably wrap the code in a class or module and pass it as argument. It's probably better.

You can do it with regular lambdas.

def say_hello(name, block = ->(name) { puts "Hi, #{name}" })
  block.call(name)
end 

say_hello("Sergio")
say_hello("Ivan", ->(name) { puts "Where are you from, #{name}?"})
# >> Hi, Sergio
# >> Where are you from, Ivan?

Not sure if you can do this with blocks, though. A block is not an ordinary parameter.

No, you can't provide a default block value in a method definition. You can, however, achieve the equivalent behavior through the use of block_given? within the body of the method, as follows:

def say_hello(name, &block)
  block = ->(name) { puts "Hi, #{name}" } unless block_given?
  # do something
end

However, in this scenario you can't utilize yield to invoke any block that is passed in, since it won't be there in the default case. You'll have to invoke the block Proc object, as in block.(name) .

Some answers suggest using block_given? , but since there is no possibility that a block would be nil or false when it is given, you can simply use ||= .

def say_hello(name, &block)
  block ||= ->(name){puts "Hi, #{name}"}
  # do something
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM