Computer >> Máy Tính >  >> Lập trình >> Ruby

Đóng cửa trong Ruby:Khối, Procs và Lambdas

Trong Ruby Magic, chúng tôi thích đi sâu vào điều kỳ diệu đằng sau những thứ chúng tôi sử dụng hàng ngày để hiểu cách chúng hoạt động. Trong ấn bản này, chúng ta sẽ khám phá sự khác biệt giữa khối, procs và lambdas.

Trong ngôn ngữ lập trình với các hàm hạng nhất, các hàm có thể được lưu trữ trong các biến và được chuyển làm đối số cho các hàm khác. Các hàm thậm chí có thể sử dụng các hàm khác làm giá trị trả về của chúng.

Đóng là một hàm hạng nhất với một môi trường. Môi trường là ánh xạ tới các biến đã tồn tại khi quá trình đóng được tạo. Việc đóng sẽ giữ lại quyền truy cập vào các biến này, ngay cả khi chúng được xác định trong một phạm vi khác.

Ruby không có các hàm hạng nhất, nhưng nó có các bao đóng ở dạng khối, procs và lambdas. Các khối được sử dụng để chuyển các khối mã tới các phương thức và procs và lambda cho phép lưu trữ các khối mã trong các biến.

Khối

Trong Ruby, khối là các đoạn mã có thể được tạo để thực thi sau này. Các khối được chuyển tới các phương thức mang lại chúng trong doend từ khóa. Một trong nhiều ví dụ là #each phương thức này lặp lại trên các đối tượng có thể liệt kê.

[1,2,3].each do |n|
  puts "#{n}!"
end
 
[1,2,3].each { |n| puts "#{n}!" } # the one-line equivalent.

Trong ví dụ này, một khối được chuyển đến Array#each , chạy khối cho từng mục trong mảng và in nó ra bảng điều khiển.

def each
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

Trong ví dụ đơn giản này về Array#each , trong while vòng lặp, yield được gọi để thực thi khối được truyền cho mọi mục trong mảng. Lưu ý rằng phương thức này không có đối số, vì khối được truyền ngầm cho phương thức.

Khối ẩn và yield Từ khóa

Trong Ruby, các phương thức có thể nhận các khối một cách hoàn toàn và rõ ràng. Truyền khối ngầm hoạt động bằng cách gọi yield từ khóa trong một phương thức. yield từ khóa là đặc biệt. Nó tìm và gọi một khối đã truyền, vì vậy bạn không cần phải thêm khối vào danh sách các đối số mà phương thức chấp nhận.

Bởi vì Ruby cho phép truyền khối ngầm định, bạn có thể gọi tất cả các phương thức với một khối. Nếu nó không gọi yield , khối bị bỏ qua.

irb> "foo bar baz".split { p "block!" }
=> ["foo", "bar", "baz"]

Nếu phương thức được gọi does lợi nhuận, khối đã truyền được tìm thấy và được gọi với bất kỳ đối số nào đã được chuyển đến yield từ khóa.

def each
  return to_enum(:each) unless block_given?
 
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

Ví dụ này trả về một bản sao của Enumerator trừ khi một khối được đưa ra.

yieldblock_given? từ khóa tìm khối trong phạm vi hiện tại. Điều này cho phép truyền các khối một cách ngầm định, nhưng ngăn mã truy cập trực tiếp vào khối vì nó không được lưu trữ trong một biến.

Chuyển khối một cách rõ ràng

Chúng ta có thể chấp nhận một cách rõ ràng một khối trong một phương thức bằng cách thêm nó làm đối số bằng cách sử dụng một tham số dấu và (thường được gọi là &block ). Vì khối bây giờ là rõ ràng, chúng tôi có thể sử dụng #call phương thức trực tiếp trên đối tượng kết quả thay vì dựa vào yield .

&block đối số không phải là đối số thích hợp, vì vậy việc gọi phương thức này bằng bất kỳ thứ gì khác ngoài một khối sẽ tạo ra ArgumentError .

def each_explicit(&block)
  return to_enum(:each) unless block
 
  i = 0
  while i < size
    block.call at(i)
    i += 1
  end
end

Khi một khối được chuyển như vậy và được lưu trữ trong một biến, nó sẽ tự động được chuyển đổi thành proc .

Procs

"Proc" là một bản sao của Proc lớp, chứa một khối mã được thực thi và có thể được lưu trữ trong một biến. Để tạo một chương trình, bạn gọi Proc.new và vượt qua nó một khối.

proc = Proc.new { |n| puts "#{n}!" }

Vì một proc có thể được lưu trữ trong một biến, nó cũng có thể được truyền cho một phương thức giống như một đối số bình thường. Trong trường hợp đó, chúng tôi không sử dụng ký hiệu và vì proc được chuyển một cách rõ ràng.

def run_proc_with_random_number(proc)
  proc.call(random)
end
 
proc = Proc.new { |n| puts "#{n}!" }
run_proc_with_random_number(proc)

Thay vì tạo một proc và chuyển nó cho phương thức, bạn có thể sử dụng cú pháp tham số dấu và của Ruby mà chúng ta đã thấy trước đó và thay vào đó sử dụng một khối.

def run_proc_with_random_number(&proc)
  proc.call(random)
end
 
run_proc_with_random_number { |n| puts "#{n}!" }

Lưu ý dấu và được thêm vào đối số trong phương thức. Thao tác này sẽ chuyển đổi một khối đã truyền thành một đối tượng proc và lưu trữ nó trong một biến trong phạm vi phương thức.

Mẹo :Mặc dù rất hữu ích khi có proc trong phương pháp trong một số trường hợp, nhưng việc chuyển đổi một khối thành một proc sẽ tạo ra một lượt truy cập hiệu suất. Thay vào đó, hãy sử dụng các khối ngầm định bất cứ khi nào có thể.

#to_proc

Các ký hiệu, hàm băm và phương thức có thể được chuyển đổi thành procs bằng cách sử dụng #to_proc của chúng các phương pháp. Một cách sử dụng thường thấy của điều này là chuyển một proc được tạo từ một biểu tượng sang một phương thức.

[1,2,3].map(&:to_s)
[1,2,3].map {|i| i.to_s }
[1,2,3].map {|i| i.send(:to_s) }

Ví dụ này cho thấy ba cách gọi tương đương #to_s trên mỗi phần tử của mảng. Trong biểu tượng đầu tiên, một ký hiệu, có tiền tố là dấu và, được chuyển, tự động chuyển nó thành proc bằng cách gọi #to_proc của nó phương pháp. Hai phần cuối cho thấy proc đó có thể trông như thế nào.

class Symbol
  def to_proc
    Proc.new { |i| i.send(self) }
  end
end

Mặc dù đây là một ví dụ đơn giản, nhưng việc triển khai Symbol#to_proc hiển thị những gì đang diễn ra. Phương thức trả về một proc nhận một đối số và gửi self với nó. Kể từ self là biểu tượng trong ngữ cảnh này, nó gọi Integer#to_s phương pháp.

Lambdas

Lambdas về cơ bản là procs với một số yếu tố phân biệt. Chúng giống các phương thức "thông thường" hơn theo hai cách:chúng thực thi số lượng đối số được truyền khi chúng được gọi và chúng sử dụng trả về "bình thường".

Khi gọi một lambda mong đợi một đối số mà không có một đối số hoặc nếu bạn truyền một đối số cho lambda không mong đợi nó, Ruby sẽ tạo ra một ArgumentError .

irb> lambda (a) { a }.call
ArgumentError: wrong number of arguments (given 0, expected 1)
        from (irb):8:in `block in irb_binding'
        from (irb):8
        from /Users/jeff/.asdf/installs/ruby/2.3.0/bin/irb:11:in `<main>'

Ngoài ra, lambda xử lý từ khóa trả về giống như cách một phương thức xử lý. Khi gọi một proc, chương trình mang lại quyền điều khiển cho khối mã trong proc. Vì vậy, nếu proc trả về, phạm vi hiện tại sẽ trở lại. Nếu một proc được gọi bên trong một hàm và gọi return , hàm cũng ngay lập tức trả về.

def return_from_proc
  a = Proc.new { return 10 }.call
  puts "This will never be printed."
end

Hàm này sẽ nhường quyền kiểm soát cho proc, vì vậy khi nó trả về, hàm sẽ trả về. Gọi hàm trong ví dụ này sẽ không bao giờ in đầu ra và trả về 10.

def return_from_lambda
  a = lambda { return 10 }.call
  puts "The lambda returned #{a}, and this will be printed."
end

Khi sử dụng lambda, nó sẽ được in. Gọi return trong lambda sẽ hoạt động giống như gọi return trong một phương thức, vì vậy a biến được điền bằng 10 và dòng được in ra bảng điều khiển.

Khối, procs và lambdas

Bây giờ chúng ta đã đi sâu vào cả khối, procs và lambdas, hãy thu nhỏ lại và tóm tắt so sánh.

  • Các khối được sử dụng rộng rãi trong Ruby để truyền các bit mã cho các hàm. Bằng cách sử dụng yield từ khóa, một khối có thể được truyền ngầm mà không cần phải chuyển đổi nó thành một proc.
  • Khi sử dụng các tham số có tiền tố là dấu và, việc chuyển một khối cho một phương thức dẫn đến một proc trong ngữ cảnh của phương thức. Procs hoạt động giống như các khối, nhưng chúng có thể được lưu trữ trong một biến.
  • Lambdas là các procs hoạt động giống như các phương thức, nghĩa là chúng thực thi tính hữu ích và trả về dưới dạng các phương thức thay vì trong phạm vi mẹ của chúng.

Điều này kết thúc cái nhìn của chúng tôi về các đóng cửa trong Ruby. Có nhiều điều để tìm hiểu về các đóng như phạm vi từ vựng và ràng buộc, nhưng chúng tôi sẽ giữ điều đó cho một tập trong tương lai. Trong thời gian chờ đợi, vui lòng cho chúng tôi biết những gì bạn muốn đọc trong phần sau của Ruby Magic, các bản đóng cửa hoặc bằng cách khác tại @AppSignal.