Blocks, Procs và Enumerable trong Ruby
Enumerable mà một module rất hay có ở trong Ruby. Nó cung cấp cho chúng ta rất nhiều hàm hữu ích như each, map, inject, ... Các hàm nói trên rõ ràng, dễ đọc và dễ hiểu hơn for ở những ngôn ngữ khác. Enumerable được kết hợp với một trong những cấu trúc rất hay khác của Ruby là blocks. Ví dụ: ...
Enumerable mà một module rất hay có ở trong Ruby. Nó cung cấp cho chúng ta rất nhiều hàm hữu ích như each, map, inject, ... Các hàm nói trên rõ ràng, dễ đọc và dễ hiểu hơn for ở những ngôn ngữ khác.
Enumerable được kết hợp với một trong những cấu trúc rất hay khác của Ruby là blocks. Ví dụ:
collection.each { |item| puts item }
hoặc
collection.map do |item| item * 2 end
Một cách ngắn gọn hơn có thể bạn đã biết :
collection.all?(&:valid?)
hoặc
collection.inject(:&)
Và với bài viết này chúng ta hãy tìm hiểu rõ hơn về nó.
Blocks
Blocks cho phép các method lấy các đoạn mã tùy (function) ý như một đối số và thực thi chúng. Việc truyền 1 function dạng anonymous (hàm vô danh, tự chạy ko cần gọi) vào trong 1 hàm được sử dụng phổ biến ở nhiều ngôn ngữ. Js là 1 ví dụ điển hình của việc sử dụng callback mà có hàm lồng trong hàm
item.action(function(result) { result.otherAction(function(newResult) { console.log(newResult); }); });
Trong Ruby, bạn có biểu diễn logic "nhận mỗi phần tử của một mảng với 2 rồi trả ra kết quả" cùng cách sử dụng map của Enumerable module:
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
Procs
Khái niệm truyền một function như một đối số vào các function khác rất hay và ứng dụng rất mạnh mẽ. Các method có khả năng này được gọi là hàm bậc cao. Ruby cho phép thực hiện điều đó với Procs.
Procs là một function object của Ruby. Chúng ta có thể truyền một proc thay vì block tới bất kỳ method nào bằng cách sử dụng tiền tố &. Ví dụ :
double = Proc.new { |n| n * 2 } [1, 2, 3].map(&double) # => [2, 4, 6]
to_proc
Trong trường hợp bạn không thể ngay lặp tức chạy được Proc để truyền function Rails cung cấp cho chúng ta một trick rất hay là to_proc được gọi trước khi thực thi.
class Doubler def double(n) n * 2 end def to_proc method(:double).to_proc end end doubler = Doubler.new [1, 2, 3].map(&doubler) # => [2, 4, 6]
Symbols
Symbols khai báo một to_proc như là một function trùng tên dưới dạng symbol và truyền vào object
ví dụ
[1,2,3].map(&:to_s) # => ["1", "2", "3"]
Ở ví dụ trên thì mỗi phần tử của array sẽ được to_s
Symbols giúp chúng ta dễ viết test hơn, code dễ hiểu hơn.
Ví dụ “are all the numbers even?” có thể được thể hiện là
numbers.all?(&:even?)
thay vì
numbers.all? { |n| n.even? }
inject
inject hay reduce là một trong những method mạnh mẽ nhất được Enumerable cung cấp. Trên thực tế, mội method khác của Enumerable đều có thể viết lại dưới dạng inject. Tuy nhiên inject có một chút khác biệt. Nó cũng sử dụng block như các method khác nhưng chấp nhận một symbol thay cho một block và symbol đó sẽ được sử dụng để truyền tới object giống như to_proc
[1, 2, 3].inject(&:+) # => 6
hoặc
[1, 2, 3].inject(:+) # => 6