Ruby Method, Proc, and Block Confusion

Ruby method, Proc, and block confusion

Question 1: Blocks are not objects, they are syntactic structures; this is why they cannot be assigned to a variable. This is a privilege reserved for objects.

Question 2: Methods are not objects, so they cannot receive messages. Inversely, procs and lambdas are objects, so they cannot be invoked like methods, but must receive a message that tells them to return a value on the basis of the parameters passed with the message.

Procs and Lambdas are objects, so they can receive the call message and be assigned to names. To summarize, it is being an object that makes procs and lambdas behave in ways you find odd. Methods and blocks are not objects and don't share that behavior.

Confusion with `Call` to `block` created as `Proc` object

Procs convert missing arguments to nil whereas lambda does not.

If you want to be tolerant about errors then use Procs. Otherwise you'll want to go with lambda

l = ->(x) { x = x * 10; puts x }
=> #<Proc:0x007fada3be9468@(pry):12 (lambda)>
l.call(10, 20)
ArgumentError: wrong number of arguments (2 for 1)
from (pry):12:in `block in <main>'

Can I pass a block to a Proc?

Procs can't accept blocks as implicit arguments (the format you're trying). A proc can receive other proc objects as arguments, either explicitly, or using & arguments. Example:

a = Proc.new do |&block|
block.call
end

a.call() {puts "hi"}

yield is a bit of laguage level magic that only works in the context of a method.

Blocks & Procs in Ruby

There are a lot of things that are good about blocks. The elevator pitch: Blocks let us pass around actions the same way we normally pass around data.

The most obvious level is that they let you abstract things out into functions that wouldn't be possible otherwise. For example, let's look at a common case where you have a list of things and you want to filter it to only include items that match some criterion:

int list[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
int evenNumbers[50] = {0};
int copyIndex = 0;
for (int i = 0; i < 50; i++) {
if (list[i] % 2 == 0) {
evenNumbers[copyIndex++] = list[i];
}
}

Here's how you write that in Ruby:

list = 1..50
listCopy = list.select {|n| n.even?}

All the common busywork is moved out of your code and into a method with a meaningful name. We don't care about copying the array and going through indexes and all that — we just want a filtered list. And that's what select gives us. The block allows us to pass our custom logic into this standard method.

But iterators aren't the only place where this "hole in the middle pattern" is useful. For example, if you pass a block to File.open, it will open the file, execute the block with the file and then close the file for you.

Another thing that blocks give us is a really powerful form of callbacks. For example, without blocks, we might have to do something like this (based on how dialogs actually work in Objective-C Cocoa):

class Controller
def delete_button_clicked(item)
item.add_red_highlight
context = {:item => item}
dialog = Dialog.new("Are you sure you want to delete #{item}?")
dialog.ok_callback = :delete_OK
dialog.ok_receiver = self
dialog.cancel_callback = :cancel_delete
dialog.cancel_receiver = self
dialog.context = context
dialog.ask_for_confirmation
end

def delete_OK(sender)
delete(sender.context[:item])
sender.dismiss
end

def cancel_delete(sender)
sender.context[:item].remove_red_highlight
sender.dismiss
end
end

Yowza. With blocks, we could do this instead (based on a common pattern used in many Ruby libraries):

class Controller
def delete_button_clicked(item)
item.add_red_highlight
Dialog.ask_for_confirmation("Are you sure you want to delete #{item}?") do |response|
response.ok { delete item }
response.cancel { item.remove_red_highlight }
end
end
end

That's actually two levels of blocks — the do...end block and the two {}-style blocks. But it reads pretty naturally, doesn't it? This works because a block captures the context it's created in, so we don't need to pass around self and item.

As for Procs, they're just an object wrapper for blocks. Not very much to them.

Ruby: provide an argument while turning proc to a block

Regarding your comment:

Strange, but it swaps arguments during the performance

Actually, the argument order is preserved.

curry returns a new proc that effectively collects arguments until there are enough arguments to invoke the original method / proc (based on its arity). This is achieved by returning intermediate procs:

def foo(a, b, c)
{ a: a, b: b, c: c }
end

curried_proc = foo.curry #=> #<Proc:0x007fd09b84e018 (lambda)>
curried_proc[1] #=> #<Proc:0x007fd09b83e320 (lambda)>
curried_proc[1][2] #=> #<Proc:0x007fd09b82cfd0 (lambda)>
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}

You can pass any number of arguments at once to a curried proc:

curried_proc[1][2][3]     #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1][2, 3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2, 3] #=> {:a=>1, :b=>2, :c=>3}

Empty arguments are ignored:

curried_proc[1][][2][][3] #=> {:a=>1, :b=>2, :c=>3}

However, you obviously can't alter the argument order.


An alternative to currying is partial application which returns a new proc with lower arity by fixing one or more arguments. Unlike curry, there's no built-in method for partial application, but you can easily write your own:

my_proc = -> (arg, num) { arg * num }

def fix_first(proc, arg)
-> (*args) { proc[arg, *args] }
end

fixed_proc = fix_first(my_proc, 'foo') #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc[2] #=> "foofoo"
fixed_proc[3] #=> "foofoofoo"

[2, 3].map(&fixed_proc) #=> ["foofoo", "foofoofoo"]

Or fixing the last argument:

def fix_last(proc, arg)
-> (*args) { proc[*args, arg] }
end

fixed_proc = fix_last(my_proc, 2) #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc['foo'] #=> "foofoo"
fixed_proc['bar'] #=> "barbar"

['foo', 'bar'].map(&fixed_proc) #=> ["foofoo", "barbar"]

Of course, you are not limited to fixing single arguments. You could for example return a proc that takes an array and converts it to an argument list:

def splat_args(proc)
-> (array) { proc[*array] }
end

splatting_proc = splat_args(my_proc)
[['foo', 1], ['bar', 2], ['baz', 3]].map(&splatting_proc)
#=> ["foo", "barbar", "bazbazbaz"]

How can I pass a block/proc and variable directly to a function?

You seem to be confusing blocks and procs. A block is not a ruby object. You cannot pass it like

foo(args, block)

The only way to pass it is foo(args){...} or foo(args) do ... end. However, in method definition, you can receive a block, convert it into a proc, and accept it as one of its arguments like this:

def foo(args, &block)
...
end

On the other hand, You can pass a proc object as an argument like

foo(args, proc)

There are several ways to create a proc object, and a literal that is close to the block literal is the lambda: ->{...}, but it is different from the block syntax {...}. Using this, you can do

foo(args, ->{...})

but not

foo(args, {...})

unless {...} is a hash.

I don't get the idea behind Ruby Proc... why not just use normal Method?

A proc is different because you can store it in a variable. Therefore you can pass it as a paramater to a function, return it from a function, manipulate it, etc.

Procs, lambdas and blocks are one of the main things that make Ruby awesome.They are at the heart of Ruby's iterators for example. When you do something like:

collection.each do |item|
//process item
end

you are basically passing a block (a Proc object) to the each function.

Let's say you a bunch of arrays, that you want to process in the same way. To save you from writing the each code every single time you can do something like:

handler = Proc.new{|item| do_something_with(item)}
array1.each &handler
array2.each &handler
....
arrayn.each &handler

When you want to pass a Proc to a function as a block, you have to preceed it with an &. The same goes for when you define a function that accepts a block parameter.

Another useful way to use Proc, is in functions.

Say you have a function that instantiates an object, does some minor changes, and returns it. To make it more flexible, you can make it accept a block like so:

def new_item(param1, param2, &block)
my_item = Item.new
my_item.attribute1 = param1
my_item.attribute2 = param2
yield my_item if block_given?\
return my_item
end

Yield is where the magic happens. When that line is evaluated, the function will execute the block that you give it with my_item as a parameter. So you can do stuff like:

my_new_item = new_item(p1, p2) do |item|
item.attribute3 = some_value
end

Now, my_new_item will have its attribute3 set as well as any other modification than you do in the block.

You don't use Procs and lambdas to replace functions, you use them to augment functions.
You can have a function that returns a Proc that was built based on whatever parameters you give it. There are a lot of ways to be creative with Procs.

Ruby: Passing a Proc to method with no parameters

&phrase is not a reference. It is the Ruby annotation for passing a block explicitly. Here , it is converting the proc to the implicit block for the method call. Since every method accepts a default block as an argument, your code works.



Related Topics



Leave a reply



Submit