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

Block, Proc, và Lamdba là gì ?

  • Theo định nghĩa kỹ thuật: thì nó là ví dụ của closures trong Ruby. Có thể hiểu closures là một hàm được tạo ra từ bên trong một hàm khác (hàm cha), và nó có thể sử dụng các biến toàn cục, cục bộ của hàm cha và chính nó. Viết code kiểu closures có thể giúp code dễ quản lý hơn và linh hoạt hơn trong việc xử lý.
  • Theo định nghĩa đơn giản: thì nó là cách để chúng ta nhóm các đoạn mã muốn chạy vào một khối.
# Block
[1,2,3].each { |x| puts x*2 }   # block nằm trong dấu ngoặc nhọn

[1,2,3].each do |x|
  puts x*2                    # block cũng có thể nằm giữa do và end
end

# Proc             
p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p)              #  & sẽ nói với Ruby là đưa proc vào một block

proc = Proc.new { puts "Hello World" }
proc.call                     # bên trong của object Proc sẽ được thực thi khi sử dụng call

# Lambda            
lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

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

Qua ví dụ trên, ta có thể thấy chúng khá là giống nhau và đơn giản, nhưng vẫn có sự khác nhau. Hãy cùng tìm hiểu phía dưới.

Khác nhau giữa Blocks và Procs

  1. Procs là đối tượng, còn Block thì không

Proc là một instance của class Proc

p = Proc.new { puts "Hello World" }
p.call  # prints 'Hello World'
p.class # returns 'Proc'
a = p   # a now equals p, a Proc instance
p       # returns a proc object '#<Proc:[email protected](irb):46>'

Ngược lại, một block chỉ là một phần của cú pháp trong method được gọi. Nó không đứng một mình được mà chỉ có thể đi cùng một list các đối số

{ puts "Hello World"}       # syntax error  
a = { puts "Hello World"}   # syntax error
[1,2,3].each {|x| puts x*2} # chỉ làm việc như một phần cú phép của method được gọi
  1. Có thể truyền nhiều procs vào method

proc là một đối tượng độc lập vì vậy ta có thể truyền nhiều procs như một tham số vào method. Còn block thì giới hạn hơn nhiều, nó chỉ có thể hoạt động khi đi kèm một list các đối số.

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

a = Proc.new { puts "First proc" }
b = Proc.new { puts "Second proc" }

multiple_procs(a,b)

Khác nhau giữa Procs và Lambdas

Cả ProcsLambdas đều là đối tượng của class Proc

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

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

Tuy nhiên, chúng lại có một sự khác biệt nhỏ.

proc   # returns '#<Proc:[email protected](irb):75>'
lam    # returns '<Proc:[email protected](irb):76 (lambda)>'

Sự khác biệt đấy là:

  1. Lambdas kiểm tra số lượng đối số, trong khi procs thì không
lam = lambda { |x| puts x }    # tạo lambda với một đối số
lam.call(2)                    # in ra 2
lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)
proc = Proc.new { |x| puts x } # tạo proc với một đối số
proc.call(2)                   # in ra 2
proc.call                      # returns nil
proc.call(1,2,3)               # in ra 1, và 2 đối số còn lại sẽ bị bỏ qua
  1. LambdasProcs xử lý return khác nhau return được viết trong lambda sẽ chỉ có tác động outside khỏi lambda code
def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end

lambda_test                 # lambda_test sẽ vẫn in ra 'Hello World'

return được viết trong proc sẽ tác động outside luôn method tại điểm proc được thực thi

def proc_test
  proc = Proc.new { return }
  proc.call
  puts "Hello world"
end

proc_test                 # proc_test sẽ không in ra gì cả