Ruby Blocks & Closures

Topics
Blocks
What is a block?
How does a block look like?
How does a block get passed and executed?
Proc object
& (Ampersand) operator
lambda
Where do blocks get used?
Closure
What is a closure?


What is a block?
什麼是Block
Blocks are basically nameless functions.
Blocks是基本的匿名函數。
You can think of it as a nameless chunk of code.
你可以認爲它是一個匿名代碼塊。
You can pass a nameless function to another function (I will it target function in this presentation), and then that target function can invoke the passed-in nameless function
你可以用匿名函數和另一個函數相聯繫,然後目標函數就能調用這個匿名函數。
For example, a target function could perform iteration by passing one item at a time to the nameless function.
例如,目標函數可以執行迭代通過匿名函數。

How to Represent a Block?
怎樣書寫Block
A block can be represented in two different formats - these two formats are functionally equivalent
一個block有兩種描述方式 - 這兩種方式作用一樣
  1. puts "----First format of code block containing code fragment between { and }"
  2. [1, 2, 3].each { |n| puts "Number #{n}" }
  3. puts "----Second format of code block containing code fragment between do and end"
  4. [1, 2, 3].each do |n|
  5.   puts "Number #{n}"
  6. end
How a block is passed & executed
block怎樣編譯,執行
When a method is invoked, a block can be passed (attached)
當一個方法被調用時,block被編譯
The yield() method in the invoked method (target function) executes the passed block
  1. puts "----Define MyClass which invokes yield"
  2. class MyClass
  3.   def command()
  4.     # yield will execute the attached block to the method
  5.     yield() 
  6.   end        
  7. end
  8. puts "----Create object instance of MyClass"
  9. m = MyClass.new
  10. puts "----Call command method of the MyClass passing a block"
  11. m.command {puts "Hello World!"}
How a block receive arguments
block如何接收參數
A block can receive arguments - they are represented as comma-separated list at the beginning of the block, enclosed in pipe characters:
block能接收參數 - 參數用逗號隔開,並加上特殊字符:
  1. puts "----Define MyClass which invokes yield"
  2. class MyClass
  3.   def command1()
  4.     # yield will execute the supplied block
  5.     yield(Time.now) 
  6.   end        
  7. end
  8. puts "----Create an object instance of MyClass"
  9. m = MyClass.new
  10. puts "----Call command1 method of the MyClass"
  11. m.command1() {|x| puts "Current time is #{x}"}
A block can receive multiple arguments:
block能夠接收復雜參數:
  1. puts "---Define a method called testyield"
  2. def testyield
  3.   yield(1000, "Sang Shin")
  4.   yield("Current time is"Time.now)
  5. end
  6. puts "----Call testyield method"
  7. testyield { |arg1, arg2| puts "#{arg1} #{arg2}" }


Proc Objects
What is a Proc object?
Proc objects (Procs) are blocks of code that have been converted to “callable” objects.
Proc對象是可調用的block代碼塊。
Proc objects are considered as first-class objects in Ruby language because they can be:
Proc對象被認識是Ruby中的第一類對象 :
Created during runtime
運行期創建
Stored in data structures (saved in a variable)
在數據結構裏儲存
Passed as arguments to other functions
能傳遞參數到其他函數
Returned as the value of other functions.
能給其他函數返回值。

How To Create a Proc Object?
怎麼創建一個Proc對象?
Use new keyword of Proc class passing a block
  1. puts "----Create a Proc object and call it"
  2. say_hi = Proc.new { puts "Hello Sydney" }
  3. say_hi.call
  4. puts "----Create another Proc object and call it"
  5. Proc.new { puts "Hello Boston"}.call
How to execute a block of a Proc Object?
如何運行一個Proc對象?
Use call method of the Proc object
調用call方法

How to pass a Proc object as an argument?
如何傳遞一個Proc對象作爲一個參數?
Pass it just like any other Ruby object - hence the reason why Proc is a first-class object
可以像傳遞其它Ruby對象一樣傳遞它,因爲Proc是第一類對象
  1. puts "----Create a Proc object"
  2. my_proc = Proc.new {|x| puts x}
  3. puts "----Define a method that receives Proc object as an argument"
  4. def foo (proc_param, b)
  5.   proc_param.call(b)
  6. end
  7.  
  8. puts "----Call a method that passes a Proc object as an arugment"
  9. foo(my_proc, 'Sang Shin')
How to pass Arguments to the block(represented by the Proc object)?
如何傳遞參數給一個block
Pass arguments in a call method of the proc object
用call方法傳遞
  1. puts "----Create a Proc object"
  2. my_proc = Proc.new {|x| puts x}
  3. puts "----Define a method that receives Proc object as an argument"
  4. def foo (proc_param, b)
  5.   proc_param.call(b)
  6. end
  7.  
  8. puts "----Call a method that passes a Proc object as an arugment"
  9. foo(my_proc, 'Sang Shin')
How to Use a Proc object as a Return Value?
怎樣用一個Proc對象當作返回值?
Just like any other Ruby object
  1. puts "----Define a method that returns Proc object as a return value"
  2. def gen_times(factor)     
  3.     Proc.new {|n| n*factor }   
  4. end
  5. puts "----Assign Proc object to local variables"
  6. times3 = gen_times(3)   
  7. puts "----Execute the code block passing a parameter"
  8. puts times3.call(3)           

Proc Object works as a Closure
Proc objects (Procs) are blocks of code that have been bound to a set of local variables.  Once bound, the code may be called in different contexts and still access those variables.
Proc對象是有一組本地變量約束的代碼塊。
  1. def gen_times(factor)
  2.    return Proc.new {|n| n*factor }  # factor is local variable in a block
  3.  end
  4.  times3 = gen_times(3)               # Block has factor variable set to 3
  5.  times5 = gen_times(5)               # Block has factor variable set to 5
  6.  times3.call(12)                           #=> 36 because 12 * 3 (factor) = 36
  7.  times5.call(5)                             #=> 25 because 5 * 5 (factor) = 25
  8.  times3.call(times5.call(4))          #=> 60 because (4 * 5  )  * 3 (factor) = 60
lambda and Proc
lambda is equivalent to Proc.new - the following three statements are considered equivalent
lambda和Proc.new等價
  1. say_hi = Proc.new { puts "Hello Sydney" }
  2. say_hi = lambda { puts "Hello Sydney" }
  3. say_hi = proc { puts "Hello Sydney" }
There are a couple of differences, however
Difference #1 between lambda and Proc
第一點不同
Argument checking: A Proc object created from using lambda checks the number of arguments passed and if they do not match, throws ArgumentError exception
參數檢查:lambda會進行參數檢查,不匹配就拋出ArgumentError異常
  1. puts "----Create Proc object using lambda"
  2. lamb = lambda {|x, y| puts x + y}
  3. puts "----Create Proc object using Proc.new"
  4. pnew = Proc.new {|x, y| puts x + y}
  5. puts "----Send 3 arguments, should work"
  6. pnew.call(2, 4, 11)
  7. puts "----Send 3 arguments, throws an ArgumentError"
  8. lamb.call(2, 4, 11)
lambda and Proc Difference 2
第二點不同
How return is handled from the Proc
如何處理返回值
A return from Proc.new returns from the enclosing method (acting just like a return from a block).
Proc.new的返回值來自封閉的方,返回後跳出方法
A return from lambda acts more conventionally, returning to its caller.
lambda的返回值返回給調用者
  1. def foo
  2.   f = Proc.new { return "return from foo from inside proc" }
  3.   f.call # control leaves foo here
  4.   return "return from foo" 
  5. end
  6. def bar
  7.   f = lambda { return "return from lambda" }
  8.   f.call # control does not leave bar here
  9.   return "return from bar" 
  10. end
  11. puts foo # prints "return from foo from inside proc" 
  12. puts bar # prints "return from bar" 
& (Ampersand) Operator
How & (Ampersand) is used?
&符號如何使用?
The ampersand operator can be used to explicitly convert between blocks and Procs
&操作符用於blocks和Procs的轉換
Conversion from a block to a Proc
block到Procs的轉換
If an ampersand (&) is prepended to the last argument in the argument list of a method, the block attached to this method is converted to a Proc object and gets assigned to that last argument.

Conversion from a Proc to a block
Another use of the ampersand is the other-way conversion - converting a Proc into a block. This is very useful because many of Ruby’s great built-ins, and especially the iterators, expect to receive a block as an argument, and sometimes it’s much more convenient to pass them a Proc.
The method receives a block as a Proc object
  1. puts "----The attached block is passed as the last argument in the form of Proc object"
  2. def my_method_ampersand(a, &f)
  3.   # the block can be accessed through f
  4.   f.call(a)
  5.     
  6.   # but yield also works !
  7.   yield(a)
  8. end
  9.  
  10. puts "----Call a method with a block"
  11. my_method_ampersand("Korea") {|x| puts x}
Pass a Proc with & preceded
  1. puts "----Create a Proc object"
  2. say_hi = Proc.new { |x| puts "#{x} Hello Korea" }
  3. puts "----Define a method which expects a block NOT Proc object"
  4. def do_it_with_block
  5.   if block_given?
  6.     yield(1)
  7.   end
  8. end
  9. puts "----Call do_it_with_block method which expects a block, convert Proc object to a block"
  10. do_it_with_block(&say_hi)
Where Do Blocks Get Used?
Blocks Usage Examples
Iteration
[1, 2, 3].each {|item| puts item}
Resource management
file_contents = open(file_name) { |f| f.read }
Callbacks
widget.on_button_press do
puts “Button is pressed”
end


What is a Ruby Closure?
In Ruby, a Proc object behaves as a Closure
A Proc object maintains all the context in which the block was defined: the value of self, and the methods, variables, and constants in scope. This context is called scope information
A block of the Proc object can still use all original scope information such as the variables even if the environment in which it was defined would otherwise have disappeared. 
Ruby Closure Example
  1. # Define a method that returns a Proc object
  2. def ntimes(a_thing)
  3.   return proc { |n| a_thing * n }
  4. end
  5. # The a_thing is set to value 23 in a block.  
  6. p1 = ntimes(23)
  7. # Note that ntimes() method has returned.  The block still
  8. # has access to a_thing variable.
  9. # Now execute the block.  Note that the a_thing is still set to
  10. # 23 and the code in the block can access it, so the results is set 69 and 92
  11. puts p1.call(3)     #   69
  12. puts p1.call(4)     #   92

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章