Variables & Scope trong Ruby
This post hasn't been updated for 3 years
Hi, new members. Lần này mình muốn bàn về chủ đề Variables & Scope In Ruby. Một chủ đề khá thú vị
1. Variable Scope có ý nghĩa như thế nào?
Variable Scope rất quan trọng khi chung ta lập trình, vì nhiều lúc ta nhầm lẫn các scope với nhau, hay hiểu sai hiệu lực của variable trong scope, dẫn đến các bug không đáng có. Hoặc đôi khi mình khai báo sai kiểu biến so với mục đích sử dụng.
2. Variables in Ruby on Rails
Ta có các loại biến sau đây.
Kí tự bắt đầu | Type |
---|---|
$ | Biến global (global variable) |
@ | Biến instance (instance variable) |
[a-z] or _ | Biến local (local variable) |
[A-Z] | Biến constant (constant variable) |
@@ | Biến class (class variable) |
2.1 Biến global
Là biến được định nghĩa bắt đầu bằng dấu $, thấy $ là thấy quyến lực rồi. Nên nó có được gọi ở bất cứ chỗ nào trong chương trình của chúng ta. Cần cân nhắc khi dùng biến global vì nó có thể gọi được ở mọi nơi và chỉnh sửa ở bất kì chỗ nào. Nên rất nguy hiểm. Khó debug.
Ví dụ:
Định nghĩa 2 class sau:
class AA
$hello = "Xin Chao!"
def say_hello
puts $hello
end
end
class BB
def say_xinchao
puts $hello
end
end
Ta có thể gọi biến global từ 1 class khác Chạy dòng lệnh sau:
BB.new.say_xinchao
Kết quả:
[31] pry(main)> BB.new.say_xinchao
Xin Chao!
=> nil
Ngoài ra, có 1 số biến global mà có sẵn trong Ruby nên biết
Biến | Ý nghĩa |
---|---|
$@ | Vị trí của error cuối cùng xảy ra The location of latest error |
$_ | Chuối string cuối cùng đọc từ hàm gets The string last read by gets |
$. | Số line từ lần cuối đọc từ interpreter The line number last read by interpreter |
$& | Chuỗi string cuối cùng match với regular expression The string last matched by regexp |
$~ | Trả về kết quả expression cuối cùng, gồm luôn kết quả của sub expression The last regexp match, as an array of subexpressions |
$n | Trả về kết quả của sub expression xác định trong kết quả trước The nth subexpression in the last match (same as $~[n]) |
$= | Bật tắt chế độ case-insensitive, không còn được support từ version Ruby 1.9 The case-insensitivity flag |
$/ | Separator của record input. Default có giá trị là "\n" The input record separator |
$\ | Separator của record output. Default có giá trị là nil The output record separator |
$0 | Tên của file script đang chạy The name of the ruby script file currently executing |
$* | Các argument của command line khi gọi thực thi script The command line arguments used to invoke the script |
$$ | Process ID của interpreter The Ruby interpreter's process ID |
$? | Exit status cuối cùng của process con The exit status of last executed child process |
2.2 Biến class
Là biến bắt đầu bằng @@ (2 dấu @ liên tiếp nhau) Biến class thì nó available trong tất cả các instances của class đó. Có nghĩa là tất cả các object mà được instantiated từ class đó được sử dụng. Khác với biến global, biến global tầm vực hoạt động của nó là cả chương trình, còn biến class thì phạm vị hoạt động nó nằm trong class.
Ví dụ: Định nghĩa class person có biến class là @@count như sau
class Person
@@count = 0
def initialize
self.class.count += 1
end
def self.count
@@count
end
def self.count=(value)
@@count = value
end
end
class Twins < Person
def initialize
self.class.count += 2
end
end
Cứ mỗi lần Person new thì sẽ tăng biến count lên 1, Twins thì là 2
5.times { Person.new }
3.times { Twins.new }
p Person.count # => 5
p Twins.count # => 11
Ta thấy biến count sử dụng chung cho cả class cha và class con. Khi class con update thì giá trị thì ở class cha cũng thay đổi giá trị theo.
2.3 Biến local
Là biến được định nghĩa ở trong function (method), trong vòng lặp (loop). Và không truy xuất được bên ngoài function hay vòng lặp đó. Biến local thường bắt đầu bằng chữ cái a-z hoặc dấu _ , tất nhiên là có thế có các ký số
Ví dụ:
def test_local()
result = 1 + 2
result.times do |x|
puts a = x + 1
p local_variables
end
p local_variables
end
Chạy hàm trên ở console (rails c), được kết quả như sau:
pry(main)> test_local
1
[:x, :a, :result]
2
[:x, :a, :result]
3
[:x, :a, :result]
[:result]
=> [:result]
Ta thấy 2 biến local x và a sẽ mất sau khi ra khỏi vòng lặp times, chỉ còn lại biến result. Điều đó có nghĩa là sau khi kết thúc vòng lặp biến a và x sẽ không còn available nữa.
Nên nếu truy xuất từ bên ngoài vòng lặp bạn sẽ nhận giá trị là nil
.
2.4 Biến constant
Là biến bắt đầu bằng 1 chữ cái hoa, chỉ giữ 1 giá trị cố định không thay đổi trong suốt thời gian chạy chương trình. Cũng vì tính chất đó đôi khi ta bắt gặp người gọi constant là constant, không có gọi là constant variable, vì dù là biến nhưng nó chả có biến đổi gì.
Nhưng đó là ý nghĩa của biến constant. Còn trong Ruby thì nó thật sự không như thế, nó có thể bị change value và chỉ kèm theo 1 dòng warning.
Ví dụ:
class TestConstant
NAME = "LOL"
def change_name
NAME = "VAIN"
end
end
Nếu chạy đoạn code trong rails console thì sẽ thấy error dynamic constant assignment
.
Như đã nói ở trên không nhất thiết là toàn là chữ cái của tên biến đều là chữ hoa, chỉ cần chữ cái đầu thôi. Nên nếu chạy đoạn code dưới đây cũng xuất hiện lỗidynamic constant assignment
.
class TestConstant
Name = "LOL"
def change_name
Name = "VAIN"
end
end
Với ví dụ trên ta không thể change value của nó, ta xem 1 ví dụ khác thử
class TestConstant
Name = "LOL"
def change_name
p Name + " VAINGLORY"
end
end
class TestChangeConstant
Name = " VAINGLORY"
TestConstant::Name = "Changed"
def change_name
p TestConstant::Name + Name
end
end
Khi chạy TestChangeConstant.new.change_name
bạn sẽ thấy value đã bị thay đổi. Như vậy mình có thể change value của constant ở ngoài class mà nó được định nghĩa. Và không thể change value ngay trong class nó được định nghĩa.
- Trong Ruby on Rails thì mình hay định nghĩa các constant trong file
Setting.xml
củagem "config"
, để mình có thể change value một cách uyển chuyển trong quá trình thiết kế mà không phải sửa trực tiếp vào code controller.
2.5 Biến instance
Giống với biến class, nhưng chỉ khác có chỗ phạm vị avaiable của nó chỉ có giá trị trong instance object của class. Các biến instance nên được định nghĩa trong các hàm instance (thường là các hàm không kèm theo từ khóa self lúc khai báo hàm). Sau khi được chạy qua (được khởi tạo) nó có thể sử dụng trong các hàm instance khác của class đó một cách bình thường.
Ví dụ:
Open console với lệnh rails c
Định nghĩa 1 class AA như sau, có 4 phương thức như sau
class AA
@say = "AA"
def initialize sth #init value cho biến @say
@say = sth
end
def self.say #class method kiểm tra biến @say
p defined? @say
p @say
p instance_variables
@say
end
def say_some_thing # instance method kiểm tra biến @say
puts @say.inspect
p defined? @say
@say_new = "new"
p instance_variables
end
def say_some_thing_2 # instance method khác để kiểm tra biến @say
puts self.class.say
p instance_variables
end
end
Giờ thử test các methods mà được định nghĩa Tạo 1 object của class AA
a1 = AA.new "BB"
Sau đó chạy các câu lệnh gọi hàm sau:
a1.say_some_thing_2 #cau 1
a1.say_some_thing #cau 2
a1.say_some_thing_2 #cau 3
Coi kết quả in như thế nào nhé a1.say_some_thing_2 #cau 1 Ở kết quả này ta không phân biệt được @say ở hàm self.say và say_some_thing_2 là 1 biến hay 2 biến khác nhau
"instance-variable" #in ra từ hàm self.say
"AA" #in ra từ hàm self.say
[:@say] #in ra từ hàm self.say
AA #in ra từ hàm say_some_thing_2
[:@say] #in ra từ hàm say_some_thing_2
=> [:@say]
a1.say_some_thing #cau 2 Ở câu này ta có thêm 1 biến instance nữa là @say_new
"BB"
"instance-variable"
[:@say, :@say_new]
=> [:@say, :@say_new]
Và a1.say_some_thing_2 #cau 3 Đối với câu 3 này thì khi bạn chạy câu 2 xong thì trong instance method say_some_thing_2 có thêm biến instance @say_new, ta có thể truy xuất biến instance ở hàm say_some_thing_2 vì nó avariable.
"instance-variable" #in ra từ hàm self.say
"AA" #in ra từ hàm self.say
[:@say] #in ra từ hàm self.say
AA #in ra từ hàm say_some_thing_2
[:@say, :@say_new] #in ra từ hàm say_some_thing_2
=> [:@say, :@say_new]
Kết quả chú ý ở đây là ở Câu 3, biến instance in ra ở 2 hàm (self.say và say_some_thing_2) nó khác nhau.
Thực ra biến instance in ra ở self.say biến instance thuộc class, hay còn gọi là class instance variables Còn biến instance in ra từ hàm say_some_thing_2 là biến instance thuộc instance method, nên nó available trong các instance method, còn trong các method có từ khóa self hoặc Class name (hay gọi chung là class method) thì không có available.
Vậy biến instance @say khai báo ở hàng thứ 2 của Class AA và biến instance @say trong instance method initialize (được sử dụng trong hàm say_some_thing) là khác nhau.
- Biến instance trong các instance method sẽ available trong các object mà được tạo ra từ Class AA.
- Còn các biến class instance variable thì sao?
Tiếp ví dụ: Ta thoát khỏi rails console trước, sau đó vào lại. Thêm hàm set value cho biến @say (class instance variable - khai báo ở hàng số 2 của class AA)
class AA
@say = "AA"
def initialize sth
@say = sth
end
def self.say
p defined? @say
p @say
p instance_variables
@say
end
def self.say= sth
@say = sth
end
…(giản lược)...
end
Tiếp tục tạo object
a1 = AA.new "BB"
Sau đó vẫn chạy câu như trước mình làm
a1.say_some_thing_2
Gía trị của biến @say in ra màn hình ở Object a1 là AA
Bây giờ mình new 1 object khác a2 = AA.new "BB2"
Rồi sau đó chạy
a2.say_some_thing_2
Kết quả in ra màn hình của biến @say (biến khai báo hàng thứ 2) vẫn là AA
Tiếp theo ta change value của biến @say này. bằng class method set ta vừa thêm vào
AA.say = "CC"
Sau đó, hãy chạy lại hàm như sau theo thứ tự object là a1 > a2
a1.say_some_thing_2
a2.say_some_thing_2
Đều ra kết quả "CC"
"instance-variable"
"CC"
[:@say]
CC
[:@say, :@say_new]
=> [:@say, :@say_new]
- Mặc dù là khác object nhưng biến @say là biến instance của class. nên khi thay đổi giá trị của biến instance class thì các giá trị @say của object được new ra từ class cũng thay đổi theo. Và sau khi change value của biến class instance @say, nếu new 1 object mới từ các class AA thì biến class instance @say sẽ có value là CC, chứ không phải có giá trị là AA như lúc đầu.
3. Scope
Trên viblo cũng có các bài viết về scope rồi, nên mình sẽ tóm tắt ngắn ngọn.
3.1 Scope là gì?
Scope ở đây không phải là scope trong ActiveRecord của RoR. Scope là vùng hoạt động của model, class, method khi ta khai báo nó. Trong ruby, trình thông dịch nó chỉ hiểu những biến nào available trong scope đó, chứ nó không tìm kiếm 1 biến nằm ngoài scope mà biến đó được định nghĩa. Scope available đối với 1 biến nào đó nó còn phụ thuộc vào kiểu biến đó được định nghĩa (global, class, instance, local, constant)
3.2 Ví dụ
Ta xét ví dụ 1 biên local như sau: Tạo 1 module có 1 biên local và in ra 1 biến class
[1] pry(main)> module ExampleModule
[1] pry(main)* def example_method
[1] pry(main)* p defined? scope
[1] pry(main)* scope = "method scope"
[1] pry(main)* puts scope
[1] pry(main)* p local_variables
[1] pry(main)* p @@scope_class
[1] pry(main)* end
[1] pry(main)* end
=> :example_method
[2] pry(main)>
Sau đó tạo 1 class include module đó vào, ở class này có biến class là @@scope_class
[3] pry(main)> class ExampleClass
[3] pry(main)* @@scope_class = "class variable"
[3] pry(main)* include ExampleModule
[3] pry(main)* define_method :info do
[3] pry(main)* p local_variables
[3] pry(main)* p @@scope_class
[3] pry(main)* end
[3] pry(main)* end
=> :info
Sau đó ta thử run example_method từ object của class này thử:
[4] pry(main)> ExampleClass.new.example_method
nil
method scope
[:scope]
NameError: uninitialized class variable @@scope_class in ExampleModule
from (pry):7:in `example_method'
Ta thấy nó nhận được biến local, còn biến Class thì không thuộc scope của module, vì module đôc lập với class.
Nếu ta gọi hàm info thì ta sẽ thấy lúc này nó hiểu biến class @@scope_class
[5] pry(main)> ExampleClass.new.info
[]
"class variable"
=> "class variable"
Sau đây là hình minh họa cho sức mạnh của scope từng biến.
- Còn scope của Constant có thể ngang với scope của global variable.
Cùng coi ví dụ sau:
OUTER_CONSTANT = 9
class TestConstant
def getConstant
IN_CLASS_CONSTANT
end
IN_CLASS_CONSTANT = OUTER_CONSTANT + 1
End
Chạy 3 câu sau đây:
TestConstant.new.getConstant # => 10
TestConstant::IN_CLASS_CONSTANT # => 10
::OUTER_CONSTANT # => 9
Thì biến OUTER_CONSTANT thì nó là 1 constant mà không access qua class (hay module) nào hết. Còn biến IN_CLASS_CONSTANT có thể access trực tiếp qua Class Name (không cần class name nếu ở trong cùng class)
Kết
Xem xong bài viết này hy vọng các bạn sẽ hiểu thêm 1 chút kiến thức và biến trong ruby, nhất là instance variable.
Tham khảo:
All Rights Reserved