+11

Variables & Scope trong Ruby

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.xmlcủa gem "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:

  1. Variables and Constants - en.wikibooks.org
  2. Ruby Variable Scope - techotopia.com
  3. Class Variables - rubymonk.com

All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.