Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Làm Chủ Ruby: Hướng Dẫn Toàn Diện Về Blocks, Procs, Và Lambdas

Ruby blocks, procs & lambdas.

Chúng là gì?

Chúng hoạt động như thế nào?

Chúng khác nhau như thế nào?

Bạn sẽ học điều đó và nhiều hơn nữa đọc bài này!

Mục Lục

  • 1 Hiểu Ruby Blocks
  • 2 Từ Khóa Yield Ruby
  • 3 Implicit vs Explicit Blocks
  • 4 Cách Kiểm Tra Block Có Được Truyền
  • 5 Lambda Là Gì?
  • 6 Lambdas vs Procs
  • 7 Closures
  • 8 Lớp Binding
  • 9 Video Tutorial
  • 10 Tổng Kết
    • 10.1 Related

Hiểu Ruby Blocks

Ruby blocks là hàm vô danh nhỏ có thể truyền vào phương thức.

Blocks nằm trong câu lệnh do / end hoặc giữa ngoặc nhọn {}, và có thể có nhiều đối số.

Tên đối số định nghĩa giữa hai ký tự pipe |.

Nếu từng dùng each trước đây, bạn đã dùng blocks!

Ví dụ:

# Form 1: khuyến khích cho block một dòng
[1, 2, 3].each { |num| puts num }
^^^^^ ^^^^^^^^ block
block arguments body
# Form 2: khuyến khích cho block nhiều dòng
[1, 2, 3].each do |num|
  puts num
end

Ruby block hữu ích vì cho phép lưu một chút logic (code) và dùng sau.

Có thể là ghi dữ liệu vào tệp, so sánh hai phần tử, hoặc in thông báo lỗi.

Từ Khóa Yield Trong Ruby

yield trong Ruby nghĩa là gì?

Yield là từ khóa Ruby gọi block khi bạn dùng nó.

Đó là cách phương thức SỬ DỤNG blocks!

Khi dùng từ khóa yield, code bên trong block sẽ chạy và làm việc của nó.

Giống như khi gọi phương thức Ruby thông thường.

Ví dụ:

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

Chạy bất kỳ block truyền cho print_once, kết quả "Block is being run" in ra màn hình.

Bạn có biết…

yield có thể dùng nhiều lần?

Mỗi lần gọi yield, block sẽ chạy, giống như gọi lại cùng phương thức.

Ví dụ:

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

Và giống như phương thức…

Bạn có thể truyền bất kỳ số lượng đối số cho yield.

Ví dụ:

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

Các đối số này trở thành đối số của block.

Trong ví dụ này là number.

Implicit vs Explicit Blocks

Blocks có thể "explicit" (tường minh) hoặc "implicit" (ngầm hiểu).

Explicit nghĩa là bạn đặt tên nó trong danh sách tham số.

Bạn có thể truyền explicit block cho phương thức khác hoặc lưu vào biến dùng sau.

Ví dụ:

def explicit_block(&block)
  block.call # giống yield
end
explicit_block { puts "Explicit block called" }

Chú ý tham số &block…

Đó là cách định nghĩa tên block!

Cách Kiểm Tra Block Có Được Truyền

Nếu thử yield không có block sẽ gặp lỗi no block given (yield).

Kiểm tra block có được truyền bằng phương thức block_given?.

Ví dụ:

def do_something_with_block
  return "No block given" unless block_given?
  yield
end

Ngăn lỗi nếu ai đó gọi phương thức không có block.

Lambda Là Gì?

Lambda là cách định nghĩa block & tham số với cú pháp đặc biệt.

Bạn lưu lambda vào biến dùng sau.

Cú pháp định nghĩa Ruby lambda:

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

Cũng dùng cú pháp thay thế: lambda thay vì ->.

Định nghĩa lambda không chạy code bên trong, giống như định nghĩa phương thức không chạy phương thức, cần dùng phương thức call.

Ví dụ:

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

Có cách khác call lambda, tốt biết chúng tồn tại, tuy nhiên khuyến khích dùng call cho rõ ràng.

Danh sách:

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

Lambdas cũng nhận đối số, ví dụ:

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

Nếu truyền sai số lượng đối số cho lambda, nó ném exception, giống phương thức thường.

Lambdas vs Procs

Procs là khái niệm rất tương tự…

Khác biệt một là cách tạo chúng.

Ví dụ:

my_proc = Proc.new { |x| puts x }

Không có lớp Lambda riêng. lambda chỉ là đối tượng Proc đặc biệt. Nếu xem instance methods từ Proc, thấy phương thức lambda?.

Bây giờ:

Proc hành xử khác lambda, đặc biệt về đối số:

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

Khác biệt khác giữa procs & lambdas là phản ứng với câu lệnh return.

lambda sẽ return bình thường, như phương thức thường.

Nhưng proc sẽ cố return từ ngữ cảnh hiện tại.

Điều mình ý:

Chạy code sau, sẽ thấy proc ném exception LocalJumpError.

Lý do là không thể return từ ngữ cảnh top-level.

Thử này:

# 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}"

Nếu proc bên trong phương thức, gọi return tương đương return từ phương thức đó.

Được chứng minh trong ví dụ sau.

def call_proc
  puts "Before proc"
  my_proc = Proc.new { return 2 }
  my_proc.call
  puts "After proc"
end
p call_proc # In "Before proc" nhưng không in "After proc"

Tóm tắt khác biệt procs và lambdas:

  • Lambdas định nghĩa với -> {}, procs với Proc.new {}.
  • Procs return từ phương thức hiện tại, lambdas return từ chính lambda.
  • Procs không quan tâm số đối số đúng, lambdas ném exception.

Nhìn danh sách này, thấy lambdas gần với phương thức thường hơn procs.

Closures

Ruby procs & lambdas cũng có thuộc tính đặc biệt khác. Khi tạo Ruby proc, nó bắt获 phạm vi thực thi hiện tại.

Khái niệm này, đôi khi gọi là closure, nghĩa là proc mang theo giá trị như biến cục bộ và phương thức từ ngữ cảnh định nghĩa nó.

Chúng không mang giá trị thực, mà tham chiếu đến chúng, nên nếu biến thay đổi sau khi proc tạo, proc luôn có phiên bản mới nhất.

Xem ví dụ:

def call_proc(my_proc)
  count = 500
  my_proc.call
end
count = 1
my_proc = Proc.new { puts count }
p call_proc(my_proc) # Điều này in gì?

Trong ví dụ có biến cục bộ count = 1.

Cũng có proc tên my_proc, và phương thức call_proc chạy (qua call) proc/lambda truyền vào.

Bạn nghĩ chương trình in gì?

Khá như 500 là kết luận logic nhất, nhưng do hiệu ứng 'closure' sẽ in 1.

Điều này xảy ra vì proc dùng giá trị count từ nơi proc được định nghĩa, bên ngoài định nghĩa phương thức.

Lớp Binding

Ruby procs & lambdas lưu thông tin phạm vi ở đâu?

Hãy nói về lớp Binding…

Khi tạo đối tượng Binding qua phương thức binding, bạn tạo 'neo' đến điểm này trong code.

Mọi biến, phương thức & lớp định nghĩa tại điểm này sẽ khả dụng sau qua đối tượng này, ngay cả khi bạn ở phạm vi hoàn toàn khác.

Ví dụ:

def return_binding
  foo = 100
  binding
end
# Foo khả dụng nhờ binding,
# dù ta ngoài phương thức định nghĩa nó.
puts return_binding.class
puts return_binding.eval('foo')
# Nếu in foo trực tiếp sẽ lỗi.
# Lý do foo chưa bao giờ định nghĩa bên ngoài phương thức.
puts foo

Nói cách khác, thực thi dưới ngữ cảnh đối tượng binding giống như code ở cùng nơi binding được định nghĩa (nhớ ẩn dụ 'neo').

Bạn không cần dùng đối tượng binding trực tiếp, nhưng tốt biết điều này tồn tại 🙂

Video Tutorial

Tổng Kết

Bài này bạn học cách block hoạt động, khác biệt Ruby procs & lambdas và hiệu ứng "closure" xảy ra khi tạo block.

Một điều chưa đề cập là phương thức curry.

Phương thức này cho phép truyền một phần hoặc tất cả đối số yêu cầu.

Nếu chỉ truyền một phần đối số, bạn nhận proc mới với đối số đã "pre-loaded", khi đủ đối số thì proc thực thi.

Hy vọng bạn thích bài viết!

Đừng quên đăng ký form dưới và chia sẻ với bạn bè 🙂