+5

Ruby closure

Là một Ruby Web Developer, chắc chắn các bạn đã nghe và biết đến khái niệm Closure in JavaScript. Vậy còn Ruby Closure là gì?, cách sử dụng và cơ chế hoạt động của nó như thế nào là nội dung sẽ được đề cập đến trong bài viết này.

Thế nào là Closure?

Closure được hiểu là functions được ràng buộc với môi trường mà chúng được định nghĩa. Nó giữ lại quyền truy cập vào các biến nằm trong phạm vi khi chúng được tạo ra, chứ không phải trong phạm vi khi closure được gọi. Về cơ bản, có thể hiểu Closure cơ bản là 1 function mà:

  • Có thể xử lý như 1 biến, nghĩa là được gán cho 1 biến khác, được truyền đi như 1 đối số phương thức.
  • Ghi nhớ các giá trị của tất cả các biến nằm trong phạm vi khi 1 function được định nghĩa và có thể truy cập các biến này ngay cả khi chúng được thực thi trong 1 phạm vi khác.

Ví dụ:

original = 1
def retrieve local_arg
  Proc.new {local_arg}
end

p = retrieve original
original = nil

p.call #=> 1

Qua ví dụ này bạn có thể thấy, ngay cả khi gán lại giá trị của biến original nhưng khi sử dụng block để gọi ra thì giá trị ban đầu của nó vẫn không thay đổi.

Vậy cơ chế là gì?

Làm thế nào mà Closure có thể "giữ lại" giá trị của các biến trong phạm vi mà nó định nghĩa??? Có 2 cách để làm điều này:

  • Closure sẽ tạo ra 1 bản sao của tất cả các biến mà nó cần khi được định nghĩa. Các bản sao này sẽ được gọi ra khi closure được gọi ở nơi nào đó.
  • Closure sẽ kéo dài "tuổi thọ" của tất cả các biến mà nó cần. Nó sẽ không sao chép chúng, nhưng sẽ giữ lại 1 tham chiếu đến chúng.

Blocks, Procs & Lambdas

Blocks

  • Block, trong thực tế nó là 1 anonymous function, là hình thức phổ biến nhất của closure được sử dụng trong Ruby
  • Dấu hiệu nhận biết là 2 block: được định nghĩa sau tham số cuối cùng của 1 method, đặt trong trong cặp dấu {#something***}* hoặc do ... end
  • Mỗi method chỉ có thể nhận 1 đối số block duy nhất
  • Ví dụ:
outer = 1
def method_name
  inner = 9
  yield inner
  puts "inner var = #{inner}"
end

method_name {|inner| outer += inner}
puts "outer var = #{outer}"

Kết quả là:

#=> inner var = 9
#=> outer var = 10

=> Vậy câu hỏi đặt ra ở đây là: Vì sao không định nghĩa 1 method return giá trị thay vì dùng cách trên? Trong những ví dụ đơn giản thì có thể, tuy nhiên nếu bạn muốn trả về nhiều hơn 1 variables thì điều này sẽ không thể. Trong khi đó bạn có thể dễ dàng yield multiple variables đến 1 block. Một điều quan trọng hơn là method thì không có quyền truy cập vào các biến xung quanh tại nơi mà nó được định nghĩa

Procs

  • Proc là 1 closure và 1 object mà có thể ràng buộc với 1 biến và tái sử dụng
  • Proc được xác đinh bằng cách gọi: Proc.new hoặc proc theo sau bởi 1 block: proc { ... }, hoặc sử dụng phương thức to_proc của 1 Method, Proc, hoặc Symbol
  • 1 proc có thể được goi thông qua method call của nó
  • Ví dụ:
outer = 1

def method_name a_proc
  inner = 9
  a_proc.call inner
  
  puts "inner var = #{inner}"
  puts "argument is  a #{a_proc.class}"
end

method_name proc {|inner| outer += inner}
# Hoặc có thể viết: method_name Proc.new {|inner| outer += inner}
puts "outer var #{outer}"

Và đây là kết quả thu được:

#=> inner var = 9
#=> argument is Proc
#=> outer var = 10
  • Thêm 1 ví dụ khác để thấy được sức mạnh của closure:
outer = 1

def m a_var
    inner = 9
    puts "inner var = #{inner}"
    proc {inner + a_var}
end

p = m outer
puts "p is a #{p.class}"

outer = 0
puts "changed outer to #{outer}"

puts "result of proc call: #{p.call}"

Kết quả:

#=> inner var = 9
#=> p is a Proc
#=> changed outer to 0
#=> result of proc call: 10

Lambdas

  • Lambda là 1 hình thức "nghiêm ngặt" hơn của Proc
  • Định nghĩa: dùng từ khóa lambda hoặc -> ( ... )
  • Sự khác nhau giữa lambda vs proc là gì: Lamda "nghiêm ngặt" hơn trong việc kiểm tra đối số Lamda có thể trả về giá trị bằng từ khóa return
  • Ví dụ:
outer = 1

def m a_var
    inner = 99
    puts "inner var = #{inner}"
    lambda {inner + a_var}
end

p = m(outer)
puts "p is a #{p.class}"

outer = 0
puts "changed outer to #{outer}"

puts "result of proc call: #{p.call}"

Kết quả trả về:

#=> inner var = 99
#=> p is a Proc
#=> changed outer to 0
#=> result of proc call: 100

Bài viết mang đến cái nhìn tổng quan về Ruby Closure, cơ chế và các hình thức thể hiện cụ thể của nó là Blocks, Procs, & Lambda, chắc chắn rằng các bạn đã sử dụng rất nhiều trong Web app của mình

Tài liệu tham khảo

http://www.elonflegenheimer.com/2012/07/08/understanding-ruby-closures.html https://www.sitepoint.com/closures-ruby/ Cảm ơn các bạn đã theo dõi bài viết! BC ...


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí