Matthew Sainsbury
Matthew Sainsbury

Reputation: 1488

Are blocks passed as arguments?

I can pass arguments to functions like this:

func 1, 2, 3

or I can use brackets like:

func(1, 2, 3)

Later on I learned about functions like list.each which I pass (not sure if this is what's really happening) a block to operate on each element:

list.each {|x| puts x}

I assumed this just passed the block as an argument to the each function, but this doesn't seem to be the case because:

list.each( {|x| puts x} )

does not work.

I realized this when shown:

5.upto(9) {|x| puts x}

Which doesn't make sense at all if the block is simply an argument.

What's going on here? Any resource you can point me to to help explain this, and perhaps other structure things that aren't immediately obvious?

Upvotes: 4

Views: 231

Answers (2)

Alex Wayne
Alex Wayne

Reputation: 187004

methods can accept exactly one block, sort of as a special argument.

def foo
  yield 123
end

foo { |x| puts x }
#=> 123

Note how that method accepts zero arguments, but still accepts a block. That's because this syntax of hanging a block off a method is sort of a special case. Any block passed to a method can be run with the yield keyword. And you can even ask if a block was passed in with the block_given? keyword.

If you want to capture it in a local variable, you can do that with some special syntax.

def foo(a, &block)
  block.call a
end

foo(123) { |x| puts x }
#=> 123

In this case, you are capturing the block argument explicitly, with that & prefix in the argument list. You now have a variable for that block, which you can send the call message in order to execute. Just know this must appear at the end of the argument list.

Also note the parens. foo(123) { |x| puts x }. The argument list stops, the parens close and the attached block comes after. Again, because this is a special case in the syntax.

Upvotes: 4

Holger Just
Holger Just

Reputation: 55718

Blocks are indeed a bit special, but can also be used as arguments. Consider this function:

def greet
  yield "Hello"
end

greet{ |greeting| puts "#{greeting} to you" }

You could also write the exact same thing like this:

def greet(&block)
  block.call("Hello")
end

greet{ |greeting| puts "#{greeting} to you" }

# which is equivalent to:
my_proc = proc{ |greeting| puts "#{greeting}, nice to see you." }
greet(&my_proc)

In the end, blocks are a special form of procs with a special syntax which makes them more usable. But you can still access the procs and pass them around.

Upvotes: 6

Related Questions