Blocks, Procs & Lambdas

1. Understanding Blocks

Block rất phổ biến ở Ruby, bạn có thể nghĩ chúng là những hàm số vô danh có thể được truyền vào các phương thức.

Các block được đặt trong câu lệnh do / end hoặc giữa các dấu ngoặc {}, và chúng có thể có nhiều đối số. Các tên đối số được định nghĩa giữa ||.

Ví dụ:

[1, 2, 3].each { |num| puts num }

Làm thế nào một method làm việc với block, và làm thế nào nó có thể biết nếu một block đã có sẵn?

Vâng, để trả lời câu hỏi đầu tiên, bạn cần sử dụng từ khóa yield. Khi bạn sử dụng yield, code bên trong block sẽ được thực hiện.

Ví dụ:

def print_once
  yield
end
 
print_once { puts "Block is being run" }

Yield có thể được sử dụng nhiều lần:

def print_twice
  yield
  yield
end
 
print_twice { puts "Hello" }

Hoặc sử dụng yield với tham số:

def one_two_three
  yield 1
  yield 2
  yield 3
end
 
one_two_three { |number| puts number * 10 }
# 10, 20, 30

Truyển block như 1 tham số của hàm:

def explicit_block &block
  block.call # Same as yield
end
 
explicit_block { puts "Explicit block called" }

2. Lambdas vs Procs

Procs và lambdas có khái niệm rất giống nhau, một trong những khác biệt là cách bạn tạo chúng.

Trên thực tế, không có lớp Lambda chuyên dụng. Một lambda chỉ là một object Proc đặc biệt. Nếu bạn hãy xem các method instance từ Proc, bạn sẽ nhận thấy có một method lambda?

say_something = -> { puts "This is a lambda" }

Chúng ta có thể sử dụng lambda thay vì ->.

Lambda sẽ không chạy nếu không dùng method call

Ví dụ:

lam = -> {p "Hello World"}
lam.call
# "Hello World"

Ngoài ra, ta có các cách khác để gọi lambda như sau:

my_lambda = -> { puts "Lambda called" }
 
my_lambda.call
my_lambda.()
my_lambda[]
my_lambda.===

Truyền đối số vào lambda:

times_two = ->(x) { x * 2 }
times_two.call(10)
# 20

Nếu chúng ta truyền sai đối số, lambda sẽ ném ra ngoại lệ. Nhưng Proc thì không:

t = Proc.new { |x,y| puts "I don't care about arguments!" }
t.call
# "I don't care about arguments!"

Cùng chạy đoạn code sau:

# Should work
my_lambda = -> { return 1 }
puts "Lambda result: #{my_lambda.call}"
 
# Should raise exception
my_proc = Proc.new { return 1 }
puts "Proc result: #{my_proc.call}"

Proc sẽ return khỏi method mà không cần quan tâm đoạn code còn lại:

def call_proc
  puts "Before proc"
  my_proc = Proc.new { return 2 }
  my_proc.call
  puts "After proc"
end
 
p call_proc
# Prints "Before proc" but not "After proc"

Những điểm khác nhau giữa Proc và lambda:

  • Lambdas được định nghĩa bởi-> {} and procs bởi Proc.new {}.
  • Proc trả về current method, lambda trả về chính nó.
  • Proc không quan tâm argument còn Lambda thì có.