12/08/2018, 13:30

What Is the Difference Between a Block, Proc, and a Lambda in Ruby?

Ruby là một ngôn ngữ bậc cao vì vậy nó hỗ trợ rất nhiều những tính năng mạnh mẽ, một trong số đó là Block, Proc và Lambda. Chúng là những công cụ cho phép nhà phát triển có thể chuyển mã vào trong một phương thức để thực thi sau. Mặc dù rất hay sử dụng các tính năng này nhưng không phải ai cũng ...

Ruby là một ngôn ngữ bậc cao vì vậy nó hỗ trợ rất nhiều những tính năng mạnh mẽ, một trong số đó là Block, Proc và Lambda. Chúng là những công cụ cho phép nhà phát triển có thể chuyển mã vào trong một phương thức để thực thi sau. Mặc dù rất hay sử dụng các tính năng này nhưng không phải ai cũng hiểu được sự khác nhau giữa chúng.

Một blocks là một đoạn code được truyền vào trong một phương thức, và được nằm trong một cặp {...} hoặc cặp do ... end. Theo convention của Ruby thì {...} dùng cho blocks trên một dòng, còn do ... end dùng để biểu diễn blocks trên nhiều dòng. Sau đây là một ví dụ về blocks :

array = [1,2,3,4]
array.map! do |n|
  n * n
end
=> [1, 4, 9, 16]

array = [1,2,3,4]
array.map! { |n| n * n }
=> [1, 4, 9, 16]

Đặc biệt trong Ruby, có một từ khóa hết sức "kỳ diệu" là yield. Từ khóa yield được đặt ở bất kỳ dòng nào trong hàm thì sau đó khi gọi hàm thì tại vị trí có từ khóa yield sẽ thực thi khối lệnh của blocks đã được truyền vào trong hàm. Từ khóa yield cũng nhận các tham số như một phương thức bình thường. Sau đây là một ví dụ đơn giản về yield :

def calculation(a, b)
  yield(a, b)
end

puts calculation(5, 6) { |a, b| a + b } # Cộng
puts calculation(5, 6) { |a, b| a - b } # Trừ

Kết quả:

11
-1

Trong ví dụ trên, với từ khóa yield, phương thức calculation vừa có thể là phép tính cộng, vừa có thể là phép trừ tùy vào blocks truyền vào là gì.

Đối với blocks, chúng ta chỉ sử dụng được một lần mà không thể tái sử dụng lại một đoạn blocks. Để giải quết vấn đề này, trong Ruby đã có một lớp là Proc. Chúng ta có thể khai báo Proc như một đối tượng, gán vào một biến. Sau đó mỗi lần muốn thực thi một Proc, ta sẽ sử dụng phương thức call. Ví dụ sua đây, ta sẽ sử dụng Proc để tính tổng hai số bất kỳ:

p = Proc.new {|a, b| a + b}
p.call 6, 5

Kết quả:

11

Như vậy, qua ví dụ trên, ta có thể thấy một đối tượng Proc có thể được sử dụng nhiều lần để thực hiện phép cộng hai số bất kỳ.

Lambdas có chức năng giống hệt như Proc nhưng khác nhau ở hai điểm quan trọng:

  • Thứ nhất, lambdas sẽ kiểm tra số lượng tham số mà nó cần thiết. Nếu số lượng tham số không phù hợp thì sẽ bị bắn ngoại lệ ArgumentError. Ví dụ:
l = lambda { "I'm a lambda" }
l.call
=> "I'm a lambda"
l.call('arg')

Kết quả:

ArgumentError: wrong number of arguments (1 for 0)
  • Thứ hai, trong khi Proc gặp một lệnh return nó sẽ trả về kết quả của nó và không thực hiện tiếp các câu lệnh phía sau của phương thức. Ngược lại, lambdas khi gặp câu lện return sẽ trả về kết quả của biểu thức nhưng các câu lệnh sau của phương thức vẫn được thực thi. Ví dụ:
def proc_math
  Proc.new { return 1 + 1 }.call
  return 2 + 2
end
def lambda_math
  lambda { return 1 + 1 }.call
  return 2 + 2
end
proc_math # => 2
lambda_math # => 4

Như bạn đã thấy ở ví dụ trên, hàm proc_math chỉ trả về kết của của Proc, còn hàm lambda_math thì cho phép thực hiện các câu lệnh sau biểu thức lambdas vì vậy có kết quả là 4.

Những điểm khác nhau giữa Blocks, Proc, Lambdas:

  1. Proc là một đối tượng, còn Blocks thì không
  2. Blocks chỉ sử dụng một lần
  3. Lambdas sẽ kiểm tra số lượng tham số, còn Proc thì không
  4. Cách xử lý từ khóa return của LambdasProc khác nhau

0