12/08/2018, 14:01

Sự khác nhau giữa Block , Proc và Lambda trong Ruby

Đối với những developers chuyên làm việc với Ruby on Rails đa số đã từng sử dụng đến Block, Proc và Lambda hoặc đã từng nghe qua các khái niệm trên. Nhưng bạn có chắc là đã hiểu rõ được các khái niệm trên không? Bạn có biết được 3 khái niệm trên có những điểm giống nhau và khác nhau như thế nào ...

Đối với những developers chuyên làm việc với Ruby on Rails đa số đã từng sử dụng đến Block, Proc và Lambda hoặc đã từng nghe qua các khái niệm trên. Nhưng bạn có chắc là đã hiểu rõ được các khái niệm trên không? Bạn có biết được 3 khái niệm trên có những điểm giống nhau và khác nhau như thế nào không? Nếu câu trả lời của các bạn là “không chắc”, thì hãy cùng mình tìm hiểu xem chúng là gì nhé.

Block, Proc và Lambda trong Ruby được xem là các bao đóng (Closure). Vậy closure là gì?

Closure cơ bản được xem như là một chức năng (function). Điều quan trọng là function nằm bên trong Closure vẫn có thể truy xuất được tất cả các variable nằm bên trong Closure. Miễn là function còn tồn tại thì các variable bên trong Closure sẽ không bị thu dọn, để cho function có thể truy xuất chúng bất cứ khi nào nó muốn.

Trước hết chúng ta cùng xem qua ví dụ sau:

# Ví dụ về Block

[1,2,3].each { |x| puts x*2 } # block là tất cả những gì được đặt giữa 2 dấu ngoặc nhọn

[1,2,3].each do |x|
  puts x*2         # block là tất cả những gì được đặt giữa do end
end

# Ví dụ về Proc
p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p) # Ký hiệu '&' nói cho ruby biết trả về proc bên trong một block

proc = Proc.new { puts "Hello World" }
proc.call  # Các câu lệnh trong đối tượng Proc sẽ được thực hiện khi gọi

# Ví dụ về Lambda
lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

lam = lambda { puts "Hello World" }
lam.call

Thoạt nhìn, có lẽ bạn sẽ nghĩ chúng khá giống nhau, nhưng không hẳn vậy, chúng vẫn có những sự khác nhau đấy.

So sánh giữa Block, Proc

– Proc là một object và là 1 thể hiện của lớp Proc giúp cho phép gọi các phương thức cũng như gán vào biến và trả lại giá trị của proc đó trong khi block là tập hợp những khối lệnh nên không thể lưu trữ trong một biến và không phải là object.

  p = Proc.new { puts "Hello World"}
  p.call  # prints 'Hello World'
  p.class # returns 'Proc'
  a = p   # a bây giờ tương đương với p, một instance Proc
  p       # Trả về một đối tượng proc '#<Proc:0x007f96b1a60eb0@(irb):46>'

– Block chỉ là tập hợp những dòng lệnh và không có ý nghĩa gì đứng độc lập.

{ puts "Hello World"}       # Lỗi cú pháp
a = { puts "Hello World"}   # Lỗi cú pháp
[1,2,3].each {|x| puts x*2} # chỉ làm việc như một phần cú pháp của một lời gọi hàm

– Chỉ truyền được 1 block vào trong danh sách đối số của methods, còn với proc thì có thể truyền nhiều proc vào methods.

def multiple_procs(proc1, proc2)
    proc1.call
    proc2.call
  end

  p1 = Proc.new { puts "First proc" }
  p2 = Proc.new { puts "Second proc" }

  multiple_procs(p1, p2)

Proc và Lambda

Đầu tiên trước khi chúng ta cùng nhau tìm hiểu sự khác nhau của Proc và Lambda, có một điều quan trọng chúng ta cần nhớ là cả hai đều là Proc object.

proc = Proc.new { puts "Hello Proc" }
  lam = lambda { puts "Hello Lambda" }

  # check class
  proc.class # returns 'Proc'
  lam.class # return 'Proc'

– Điểm khác nhau đầu tiên giữa lambda và proc là đối các gọi hàm và đối tượng trả về. Ví dụ

# có 2 cách khai báo đối tượng proc
  proc = Proc.new { puts "Hello world" }
  another_proc = proc { puts "Hello world" } #<Proc:0x007fd7ba81bbb0@(pry):4>

  # khai báo lambda
  lam = lambda { puts "Hello world" } #<Proc:0x007fd7ba202b20@(pry):5 (lambda)>

– lambda kiểm tra số lượng tham số đầu vào và trả về một ArgumentError nếu số lượng truyền vào không đúng với tham số được khai báo trong method của nó; còn Proc thì không kiểm tra số lượng tham số đầu vào và mặc định tham số đó là nil

 lam = lambda { puts "lambda" }
  lam.call #gọi method lambda không có tham số

  => "lambda"

  lam.call("parameter")
  => ArgumentError: wrong number of arguments (1 for 0)

Để hiểu rõ cụ thể hơn, cùng xem lướt qua ví dụ cụ thể sau:

  # proc
  proc = Proc.new { |x| puts x*2 }
  proc.call(1, 2)

  => 2 #return 2

  # lambda
  lam = lambda { |x| puts x*2 }
  lam.call(1, 2)
  => ArgumentError: wrong number of arguments (2 for 1)

– Sử dụng hàm return trong lambda and proc sẽ trả về các giá trị khác nhau. Ví dụ:

def proc_method
    proc = Proc.new { return 1 + 1 }
    proc.call

    return 2 + 2
  end

  def lambda_method
    lam = lambda { return 1 + 1 }
    lam.call

    return 2 + 2
  end

  // call proc and lambda method

  proc_method #=> 2
  lambda_method #=> 4

Qua ví dụ minh chứng, ta thấy khi sử dụng proc thì ngay sau khi gặp chỉ lệnh return, giá trị sẽ lặp tức trả về; ngược lại đối với lambda, sau khi nhận chỉ lệnh "return 1 + 1" biểu thức lambda vẫn tiếp tục thực hiện các biểu thức đến cuối cùng nhận được chỉ lệnh "return 2 + 2". Vì vậy sử dụng proc hay lambda phụ thuộc vào mục đích return giá trị mà bạn muốn là gì.

Kết luận

– Proc là một đối tượng, còn block thì không. – Hầu hết block xuất hiện trong một danh sách các tham số (argument). – Lambda kiểm tra số lượng tham số đầu vào và sẽ quăng ra ArgumentError nếu tham số đầu vào không đúng; proc không kiểm tra số lượng tham số đầu vào, và mặc định tham số sẽ là nil. – Tùy vào mục đích return giá trị trong method như thế nào mà chúng ta sẽ sử dụng lambda hay proc.

Hy vọng sau bài viết này các bạn sẽ có cái nhìn rõ hơn về các khái niệm Block, Proc & Lambda cũng như các tính năng mạnh mẽ mà chúng đem lại

Nguồn bài viết : http://awaxman11.github.io/blog/2013/08/05/what-is-the-difference-between-a-block/

0