⬆️ ⬇️

3. Metaprogramming patterns - 20 kyu. Closures

In the previous post, we touched on the most important concept - closure.

The essence of this concept is that in any block it seems to be “the whole world around us” as it is seen in the context where the block is created. It is more correct to say that the block does not include the entire world around (name space), but a point of view on the world around (name space) is fixed.





Re-read this paragraph again after considering the following examples.



To understand the examples, it is useful to get acquainted with the concept of a block, Proc#call Proc#call , the lambda construct, as well as the concepts of a class instance variable (instances variables are variables whose names begin with the dog) and class variables (class variables are variables whose names begin with two dogs):



So, code samples:

')

Example 1

a = 1 <br>

b = lambda { puts a }<br>

b.call # 1 <br>

a = 2 <br>

b.call # 2 <br>

# - <br>



Example 2

class Abc <br>

attr_accessor :bar <br>

def foo <br>

@bar ||= 0 <br>

x = 5 <br>

lambda { puts @bar , x, self .class; }<br>

end <br>

end <br>

<br>

x = 10 <br>

a = Abc .new<br>

b = a.foo<br>

b.call # 0, 5 Abc <br>

a.bar += 1 <br>

x = 10 <br>

b.call # 1, 5, Abc <br>

# bar a b, <br>

# ( ) -- <br>

# ; - <br>

# foo, @a x, <br>

# , <br>

# , foo . <br>

<br>



A closure occurs for any block, whether created with lambda or with a block passed to the method, either with braces or with a do ... end construct.



In the last example, we called the foo method on the instance a of some class Abc .

Inside this method, the instance variable @bar and a block is returned, which

prints this variable as well as the value of the local variable x and self.class .

After executing this code, you will see how strongly the unit is tied to its homeland, all its thoughts and motivations are there.



In the context in which the string " b.call " is b.call , the @bar variable @bar not visible (or rather, it simply does not exist in this context).

Nevertheless, the execution of the b block leads to the output of the variable @bar object a , which, as it were, is not suitable here. This is explained by the fact that the block was created in the context of the execution of the foo method of the object a , and in this context all the instance variables of the object a were visible.



Thus, the internal context of an object can be pulled out with the help of a block created inside the object and transferred as the result of a certain function to the outside.



Example 3

class Abc <br>

attr_accessor :block <br>

def do_it <br>

@a = 1 <br>

block.call<br>

end <br>

end <br>

<br>

c = 1 <br>

a = Abc .new<br>

a.block = lambda { puts " c= #{ c } " }<br>

a.do_it # 1; <br>

# - <br>

# <br>

<br>

a.block = lambda { puts " @a= #{ @a .inspect } " }<br>

a.do_it # nil, .. @ , <br>

# " " a.block. <br>

# a.block Abc#foo <br>

# Abc#foo a.block <br>





Repeat the same thing, only now the block will be created simply as a block associated with the method, and not with the lambda construction:

class Abc <br>

def do_it (&block)<br>

@a = 1 <br>

block.call<br>

end <br>

end <br>

<br>

c = 1 <br>

a = Abc .new<br>

a.do_it {puts " c= #{ c } " } <br>

a.do_it { puts " @a= #{ @a .inspect } " }<br>

<br>





What is context?



This is a definite point of view on the namespace, from which something is visible, something is invisible, but something is seen in its own way.



For example, instance-variables of the object for which this method is visible and self equal to this object are visible from the body of the method. Instance variables of other objects are not visible.



It is useful to consider the particular expression self as some method that can be defined in its own way in each context.



The reason for changing the context is the def and class constructs. They usually lead to a change in the visibility of the instance variables, class variables and a change in the value of the expression self .



A regular block is also a new context, even if it includes the context in which it was created. A block can have its own local variables (as well as in C) and arguments (which should be interpreted as special local variables).



Actually the concept of context has its very specific mapping in Ruby - this is an object of class Binding Binding Each block has a binding , and this binding can be passed as the second argument to the eval method: “run this code in this context”:



Example 4

class Abc <br>

attr_accessor :x <br>

def inner_block <br>

lambda {| x | x * @factor }<br>

end <br>

end <br>

<br>

a = Abc .new<br>

b = a.inner_block<br>

eval ( " @factor = 7 " , b.binding)<br>

puts b[ 10 ] # 70 <br>

eval ( " @x = 6 * @factor " , b.binding)<br>

puts ax # 42





But, of course, you don’t need to write like that. To execute code in the context of an object, simply use instance_eval :



Example 5

class Abc <br>

attr_accessor :x <br>

end <br>

<br>

a = Abc .new<br>

a.instance_eval( " @factor = 7 " )<br>

a.instance_eval( " @x = 6 * @factor " )<br>

puts ax # 42





Paycheck



For such pleasure as closures, you have to pay.



Links



Source: https://habr.com/ru/post/50665/



All Articles