12/08/2018, 16:22

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
0