Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 20, 2020 09:03 pm GMT

Where Do Ruby Blocks Come From?

Its time to get reflectivetime for some deep introspectionso light a candle or two, put some Barry White on the stereo, get nice and comfortable, because were going to talk about Blocks.

Blocks in Ruby are powerful, and theyre used everywhere. You can pass blocks implicitly to enumerable methods like map and select simply by adding { ... } or do ... end after the method name. You can explicitly create closures (aka anonymous functions) by using proc, lambda, or ->() semantics and pass them around as method arguments or store them as variables. Yes, blocks are pretty magical.

But were not here to talk about how to write blocks per se or what theyre good for. Were here to talk about where they come from.

What do you mean, where they come from? They come from programmers, silly!

Well, obviouslybut I dont mean how they originate in the minds of intrepid Rubyists everywhere I mean where do they come from in the execution context of your Ruby program?

Oh OK. Yes, Id like to know that too.

Awesome sauce! So lets dive into what exactly happens when a block is first created.

Behold the Mighty Binding!

When you write a block, you arent merely defining some lines of code that will get executed later. Youre also creating a binding. A binding is the execution context in which the block will eventually be executed. It consists of:

  • the current receiver i.e., what the value of self is inside your block
  • local variables i.e., any variables that were in the scope of your program right before the block was defined

Bindings are actually instances of the Binding class, which means you can inspect the binding of any blocks Proc instance you have access to.

Wait wut?

Yep, in Ruby virtually everything is an object, even blocks (in the form of Proc instances). If youre coming from a less dynamic language (say, Javascript), prepare to have your mind blown! Heres an example:

abc = 123block = proc {}block.binding.local_variable_get(:abc) # 123xyz = 987block.binding.local_variable_get(:xyz) # NameError (local variable `xyz' is not defined for #<Binding>)block.binding.local_variables # [:block, :abc, ...]
Enter fullscreen mode Exit fullscreen mode

The reason the first variable (abc) was part of the binding and the second (xyz) wasnt is because blocks inherit their parent scope at the point where the block is defined, but anything added in that scope later isnt accessible from within the block. This is sometimes referred to as lexical scope.

However and this is important to note changes to existing variables are accessible. Check this out:

abc = 123block = proc { puts abc }block.binding.local_variable_get(:abc) # 123block.call # 123abc = 456block.binding.local_variable_get(:abc) # 456block.call # 456
Enter fullscreen mode Exit fullscreen mode

The binding stores references to local variables by name (i.e., it doesnt copy any variables), so when your variable was reassigned with a different value later, the block could still access the new value.

Of course, you can also reassign variables within a block and the new values are accessible outside the block:

abc = 1proc { abc = 2 }.callputs abc # 2
Enter fullscreen mode Exit fullscreen mode

But what if you want to hide a local variable from inside a block i.e., the variable isnt included in the block scope and changes dont propagate back up to the parent scope? Never fear! You can create what are called block-local variables by listing them in the blocks parameter list, preceded by a semicolon:

abc = 1proc do |;abc|  puts abc.nil? # true  abc = 2  puts abcend.call # 2puts abc # 1
Enter fullscreen mode Exit fullscreen mode

So thats pretty cool! But heres where things get a little confusing: if you call binding.local_variable_get(:abc) on the proc even when specifying abc as a block-local variable, you get back the value 1, not nil. I guess thats because the binding is telling you about the context the block has been bound to, not necessarily what the exact state of affairs will be inside the block. If you know of a way to introspect block-local variables through the Binding object, please let me know!

It is Better to Give Than to Receiver

Another thing to take a look at is the receiver of the binding. Any method calls you make implicitly or explicitly to self, as well as any instance variables you access, will all be bound to the receiver. When youre testing this out in IRB or in a basic Ruby script, the receiver will be the main object (unless you are inside of another object). Heres an example:

block = proc {}puts block.binding.receiver # mainclass MyObject  def receiver    block = proc {}    block.binding.receiver # return current scope's self, aka MyObject instance  endendputs MyObject.new.receiver.class # MyObject
Enter fullscreen mode Exit fullscreen mode

Now heres where things get really trippy. Ruby lets you change the receiver of a block! Yep, thats right: you can swap one receiver out for another and when you execute the block its self will be different than the originally bound receiver.

abc = 123block = proc do  puts abc  puts self.xyz # explicit so you can see what's going onendblock.call # NoMethodError (undefined method `xyz' for main:Object)class MyObject  def xyz    456  endendobj = MyObject.newblock.binding.receiver = objblock.call
Enter fullscreen mode Exit fullscreen mode

Run that andoh wait, oops, that doesnt work! Thats because there is no receiver= method for binding like you might expect. Fortunately, theres another way to go about things (in Ruby there almost always is!). We can use the instance_exec method of the object itself. Lets fix the code and try again:

abc = 123block = proc do  puts abc  puts self.xyzendblock.call # still causes an errorbut wait!class MyObject  def xyz    456  endendobj = MyObject.new# Time to try out instance_exec!obj.instance_exec(&block)# output: 123#         456
Enter fullscreen mode Exit fullscreen mode

That works!

So using instance_exec is very similar to using call, only you pass the block in as the first argument (just make sure to include the ampersand in front of the block variable). Any additional arguments will be passed to the block itself, same as any arguments you would give call. When you use instance_exec to execute the block proc, its then able to access the xyz method of obj whereas before there was no xyz method available. In addition, even if you use instance_exec, the block still has access to the original local variables (abc) as part of its binding.

If you wanted to get really fancy with Ruby-fu metaprogramming, you could store the blocks bound receiver in a variable, then use instance_exec in combination with method_missing so that method calls in the context of the new object would actually end up shadowing those original receivers methods. Why on earth would you want to do this? Lets just say I have a story to tell youbut well save that for a later date.

Summary

So there you have it: Ruby blocks are fun and weird and can do so much, yet they ask so little of us in return. May we learn to appreciate how much work they have to do under the hood to make it all seem so easy. And now that you know more about bindings, lexical scope, and the wizard-like power of instance_exec, you too can have precise control over exactly whats going on as you wield (and yield) procs and lambdas like a mahtsukai.


Original Link: https://dev.to/jaredcwhite/where-do-ruby-blocks-come-from-442d

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To