Khối (Block) và Đối tượng Thủ tục (Proc)
Khối mã trong Ruby là một trong những tính năng hay ho nhất của ngôn ngữ này. Nó được định nghĩa bởi tập hợp các dòng mã nằm giữa hai dấu ngoặc nhọn { } hoặc từ khóa do end, và bạn có thể đính các khối mã này vào các câu lệnh gọi phương thức như là một tham số vậy. Vị trí của khối mã bắt đầu từ dòng chứa tham số cuối cùng của lệnh gọi phương thức (hoặc dòng chứa dấu đóng ngoặc của dãy tham số). Khối mã sẽ không được thi hành ngay khi trình dịch gặp nó. Thay vào đó, Ruby ghi nhớ ngữ cảnh mà khối mã tồn tại (các biến cục bộ, đối tượng hiện thời .v.v.) rồi mới đi vào bên trong phương thức.
Quy ước là dùng dấu ngoặc nhọn cho khối mã 1 dòng, và các từ khóa do – end cho các khối mã nhiều dòng. Nên nhớ rằng cách viết dùng dấu ngoặc nhọn có độ ưu tiên cao hơn cách viết do..end.
Matz nói rằng bất kì phương thức nào cũng có thể được gọi với một khối mã làm tham số ngầm. Bên trong phương thức, bạn có thể gọi khối mã sử dụng từ khóa yield và một giá trị nào đó.
Một khi đã tạo được khối mã, bạn có thể lồng nó vào lệnh gọi bên trong phương thức. Thường thì khối mã đưa vào trong các phương thức là các đối tượng không tên, được tạo ra ngay lúc đó. Ví dụ, trong đoạn mã sau, khối mã chứa lệnh puts “Hello” được gắn vào lệnh gọi phương thức greet.
greet {puts 'Hello'}
Nếu phương thức chứa các tham số, chúng sẽ được đặt lên trước khối mã.
verbose_greet(“PuneRuby”) {puts 'Hello'}
Khi được gọi và truyền vào một khối mã, phương thức có thể cho gọi khối mã đó một hoặc nhiều lần sử dụng lệnh <code>yield</code>.
Chương trình <code>p022codeblock.rb</code> dưới đây minh họa cho điều này
[code='ruby']
=begin
Ruby Code blocks are chunks of code between braces or
between do- end that you can associate with method invocations
=end
def call_block
puts 'Start of method'
# you can call the block using the yield keyword
yield
yield
puts 'End of method'
end
call_block {puts 'In the block'}
Kết quả cho ra là:
>ruby p022codeblock.rb Start of method In the block In the block End of method >Exit code: 0
Khi bạn truyền vào một khối mã khi gọi phương thức, thì bên trong phương thức bạn có thể kiểm soát được khối mã đó: tạm dừng thi hành phương thức, thi hành khối mã, rồi lại tiếu tục thi hành phương thức ngay sau khi lệnh yield được gọi.
Bạn cũng có thể truyền vào các tham số cho lệnh gọi yield: các tham số này sẽ lại được truyền vào khối mã. Bên trong khối mã, bạn liệt kê danh sách các tham số truyền vào giữa hai dấu gạch dọc |.
Ví dụ p023codeblock2.rb dưới đây minh họa cho điều đó.
# You can provide parameters to the call to yield:
# these will be passed to the block
def call_block
yield('hello', 99)
end
call_block {|str, num| puts str + ' ' + num.to_s}
Kết quả là :
>ruby p023codeblock2.rb hello 99 >Exit code: 0
Giá trị trả về của một khối (cũng giống của một phương thức) là giá trị của biểu thức cuối cùng được xét đến trong khối mã. Giá trị trả về này có thể được sử dụng bên trong phương thức, và có thể truy nhập thông qua giá trị trả về của yield.
Phương thức block_given? sẽ trả về giá trị true nếu khối mã được truyền vào trong phương thức. Tham khảo ví dụ sau:
def try
if block_given?
yield
else
puts "no block"
end
end
try # => "no block"
try { puts "hello" } # => "hello"
try do puts "hello" end # => "hello"
Khối mã không phải là một đối tượng, nhưng chúng có thể được chuyển thành đối tượng thuộc lớp Proc. Có thể làm điều này bằng cách gọi phương thức lambda thuộc module Kernel. Một khối tạo bởi lambda sẽ hoạt động như một phương thức. Nếu bạn không gọi đúng số tham số truyền vào, khối mã sẽ không được thi hành.
prc = lambda {“hello”}
Các đối tượng Proc là các khối mã được gắn vào cùng với tập hợp các biến cục bộ. Lớp Proc có một phương thức gọi khối mã. Chương trình p024proccall.rb minh họa điều này.
# Blocks are not objects
# they can be converted into objects of
# class Proc by calling lambda method
prc = lambda {puts 'Hello'}
# method call invokes the block
prc.call
# another example
toast = lambda do
puts 'Cheers'
end
toast.call
Kết quả là
>ruby p024proccall.rb Hello Cheers >Exit code: 0
Nên nhớ rằng bạn chỉ có thể truyền đối tượng thủ tục (proc) vào một phương thức, chứ không thể truyền phương thức làm tham số của một phương thức khác. Tương tự như vậy, một phương thức có thể có giá trị trả lại là một proc, nhưng không thể trả lại một phương thức khác.
Ví dụ p025mtdproc.rb dưới đây minh họa phương thức được truyền proc vào làm tham số như thế nào:
=begin You cannot pass methods into other methods (but you can pass procs into methods), and methods cannot return other methods (but they can return procs) =end def some_mtd some_proc puts 'Start of mtd' some_proc.call puts 'End of mtd' end say = lambda do puts 'Hello' end some_mtd say
Kết quả:
>ruby p025mtdproc.rb Start of mtd Hello End of mtd >Exit code: 0
Đây là một ví dụ nữa về truyền tham số vào khói mã sử dụng lambda.
aBlock = lambda { |x| puts x }
aBlock.call 'Hello World!'
# output is: Hello World!
Bạn cũng có thể tìm hiểu thêm về Khối mã và Đối tượng Thủ tục tại bài viết của Fabio Akita.
