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

Bindings and Lexical Scope trong Ruby

Chúc mừng năm mới và chào mừng trở lại với Ruby Magic! Trong tập mùa đông này, chúng ta sẽ đi sâu vào các ràng buộc và phạm vi. Vì vậy, hãy đeo ván trượt của bạn và theo chúng tôi vào sâu trong rừng.

Lần trước, chúng ta đã xem xét các bao đóng trong Ruby bằng cách so sánh các khối, procs và lambdas. Ngoài sự khác biệt giữa ba loại, chúng tôi đã đề cập đến những gì định nghĩa một đóng cửa .

Đó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 vẫn giữ 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.

Chúng tôi đã khám phá chức năng tương đương của Ruby với các hàm hạng nhất, nhưng chúng tôi đã bỏ qua môi trường một cách thuận tiện . Trong tập này, chúng ta sẽ xem xét cách môi trường đó hoạt động đối với các bao đóng, các lớp và các cá thể lớp bằng cách kiểm tra cách Ruby xử lý phạm vi từ vựng thông qua ràng buộc của nó .

Phạm vi từ vựng

Trong lập trình, phạm vi đề cập đến các ràng buộc có sẵn ở một phần cụ thể của mã. Một ràng buộc hoặc ràng buộc tên , liên kết tên với tham chiếu bộ nhớ, giống như tên của biến với giá trị của nó. Phạm vi xác định cái gì self nghĩa là các phương thức có thể được gọi và các biến có sẵn.

Ruby, giống như hầu hết các ngôn ngữ lập trình hiện đại, sử dụng một phạm vi tĩnh, thường được gọi là phạm vi từ vựng (trái ngược với phạm vi động). Phạm vi hiện tại dựa trên cấu trúc của mã và xác định các biến có sẵn tại các phần cụ thể của mã. Điều này có nghĩa là phạm vi thay đổi khi mã nhảy giữa các phương thức, khối và lớp — ví dụ như tất cả chúng đều có thể có các biến cục bộ khác nhau.

def bar
  foo = 1
  foo
end
 
bar #  => 1

Trong phương pháp này, chúng tôi tạo một biến cục bộ bên trong một phương thức và in nó ra bảng điều khiển. Biến có trong phạm vi bên trong phương thức, vì nó được tạo ở đó.

foo = 1
 
def bar
  foo
end
 
bar # => NameError (undefined local variable or method `foo' for main:Object)

Trong ví dụ này, chúng tôi tạo biến bên ngoài phương thức. Khi chúng tôi gọi biến bên trong một phương thức, chúng tôi gặp lỗi, vì biến đó nằm ngoài phạm vi . Các biến cục bộ có phạm vi chặt chẽ, có nghĩa là một phương thức không thể truy cập vào một biến bên ngoài chính nó trừ khi nó được truyền dưới dạng đối số.

@foo = 1
 
def bar
  @foo
end
 
bar #  => 1

Mặc dù các biến cục bộ có sẵn cục bộ, biến phiên bản có sẵn cho tất cả các phương thức của một cá thể lớp.

Phạm vi kế thừa và liên kết Proc

Như chúng ta đã thấy trong các ví dụ trước, phạm vi dựa trên vị trí trong mã. Một biến cục bộ được định nghĩa bên ngoài một phương thức không nằm trong phạm vi bên trong phương thức nhưng có thể được tạo sẵn bằng cách biến nó thành một biến thể hiện. Các phương thức không thể truy cập các biến cục bộ được xác định bên ngoài chúng vì các phương thức có phạm vi riêng, với các ràng buộc riêng của chúng.

Procs (bao gồm các khối và lambda, theo phần mở rộng) là khác nhau. Bất cứ khi nào một proc được khởi tạo, một liên kết được tạo sẽ kế thừa các tham chiếu đến các biến cục bộ trong ngữ cảnh khối được tạo.

foo = 1
Proc.new { foo }.call # => 1

Trong ví dụ này, chúng tôi đặt một biến có tên là foo thành 1 . Bên trong, đối tượng Proc được tạo trên dòng thứ hai sẽ tạo ra một ràng buộc mới. Khi gọi proc, chúng ta có thể yêu cầu giá trị của biến.

Vì liên kết được tạo khi khởi tạo proc, chúng tôi không thể tạo proc trước khi xác định biến, ngay cả khi khối không được gọi cho đến khi biến được xác định.

proc = Proc.new { foo }
foo = 1
proc.call # => NameError (undefined local variable or method `foo' for main:Object)

Gọi proc sẽ tạo ra NameError vì biến không được xác định trong các ràng buộc của chương trình. Do đó, bất kỳ biến nào được truy cập trong một proc phải được xác định trước khi proc được tạo hoặc chuyển dưới dạng đối số.

foo = 1
proc = Proc.new { foo }
foo = 2
proc.call # => 2

Tuy nhiên, chúng tôi có thể thay đổi biến sau khi nó đã được xác định trong ngữ cảnh chính vì ràng buộc của proc giữ một tham chiếu đến nó thay vì sao chép nó.

foo = 1
Proc.new { foo = 2 }.call
foo #=> 2

Trong ví dụ này, chúng ta có thể thấy rằng foo biến trỏ đến cùng một đối tượng khi ở trong proc cũng như bên ngoài của nó. Chúng tôi có thể cập nhật nó bên trong proc để cập nhật cả biến bên ngoài nó.

Ràng buộc

Để theo dõi phạm vi hiện tại, Ruby sử dụng ràng buộc , đóng gói ngữ cảnh thực thi tại mỗi vị trí trong mã. binding phương thức trả về một Binding đối tượng mô tả các ràng buộc ở vị trí hiện tại.

foo = 1
binding.local_variables # => [:foo]

Đối tượng liên kết có một phương thức có tên là #local_variables trả về tên của tất cả các biến cục bộ có sẵn trong phạm vi hiện tại.

foo = 1
binding.eval("foo") # => 1

Mã có thể được đánh giá trên ràng buộc bằng cách sử dụng #eval phương pháp. Ví dụ trên không hữu ích lắm, vì chỉ cần gọi foo sẽ có kết quả tương tự. Tuy nhiên, vì ràng buộc là một đối tượng có thể được truyền xung quanh, nên nó có thể được sử dụng cho một số điều thú vị hơn. Hãy xem một ví dụ.

Một ví dụ trong đời thực

Bây giờ chúng ta đã học về các ràng buộc trong sự an toàn của nhà để xe của chúng ta, chẳng hạn như đưa chúng ra ngoài dốc và chơi đùa trong tuyết. Ngoài việc Ruby sử dụng nội bộ các ràng buộc trong toàn bộ ngôn ngữ, có một số trường hợp mà các đối tượng ràng buộc được sử dụng một cách rõ ràng. Một ví dụ điển hình là ERB — hệ thống tạo khuôn mẫu của Ruby.

require 'erb'
 
x = 1
 
def y
  2
end
 
template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`")
template.result(binding) # => "x is 1, y() returns 2, self is `main`"

Trong ví dụ này, chúng tôi tạo một biến có tên x , một phương thức được gọi là y và một mẫu ERB tham chiếu đến cả hai. Sau đó, chúng tôi chuyển liên kết hiện tại đến ERB#result , đánh giá các thẻ ERB trong mẫu và trả về một chuỗi với các biến được điền vào.

Bên dưới, ERB sử dụng Binding#eval để đánh giá nội dung của từng thẻ ERB trong phạm vi của liên kết được thông qua. Một triển khai đơn giản hoạt động cho ví dụ trên có thể trông giống như sau:

class DiyErb
  def initialize(template)
    @template = template
  end
 
  def result(binding)
    @template.gsub(/<%=(.+?)%>/) do
      binding.eval($1)
    end
  end
end
 
x = 1
 
def y
  2
end
 
template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`")
template.result(binding) # => "x is 1, y() returns 2, self is `main`"

DiyErb lớp nhận một chuỗi mẫu khi khởi tạo. #result của nó phương pháp tìm tất cả các thẻ ERB và thay thế chúng bằng kết quả đánh giá nội dung của chúng. Để làm điều đó, nó gọi Binding#eval trên liên kết được thông qua, với nội dung của các thẻ ERB.

Bằng cách chuyển ràng buộc hiện tại khi gọi #result , eval các lệnh gọi có thể truy cập các biến được định nghĩa bên ngoài phương thức và thậm chí bên ngoài lớp mà không cần phải chuyển chúng một cách rõ ràng.

Có phải chúng tôi đã để mất bạn trong rừng không?

Chúng tôi hy vọng bạn thích chuyến đi trượt tuyết của chúng tôi vào rừng. Chúng tôi đã đi sâu hơn vào phạm vi và kết thúc, sau khi đánh bóng chúng. Chúng tôi hy vọng chúng tôi không để mất bạn trong rừng. Vui lòng cho chúng tôi biết nếu bạn muốn tìm hiểu thêm về các ràng buộc hoặc có bất kỳ chủ đề Ruby nào khác mà bạn muốn đi sâu vào.

Cảm ơn bạn đã theo dõi chúng tôi và vui lòng xóa bỏ những ràng buộc của bạn trước khi để chúng cho nhà phát triển tiếp theo. Nếu bạn thích những chuyến đi kỳ diệu này, bạn có thể đăng ký Ruby Magic để nhận e-mail khi chúng tôi xuất bản một bài báo mới khoảng mỗi tháng một lần.