+1

Truyền tham chiếu, tham trị trong Ruby và một số lưu ý (Phần I)

Đã khi nào các bạn tự hỏi trong Ruby các tham số được truyền dạng tham chiếu hay tham trị, có điểm gì khác so với những ngôn ngữ lập trình khác. Có lẽ các bạn đã có câu trả lời của mình 😄, vậy chúng ta cùng tìm hiểu khái niệm cơ bản này để xem cách Ruby làm việc, điều này giúp chúng ta làm việc với Ruby tốt hơn và giảm số lỗi bạn có thể gặp phải do các hành vi bất ngờ.

Đầu tiên ta sẽ tìm hiểu khái niệm biến và tham chiếu trong Ruby:

Biến và tham chiếu

Một đối tượng bao gồm dữ liệu(thuộc tính) và các hành vi để thao tác với dữ liệu của nó(phương thức). Nó có thể được đơn giản, giống như object true kiểu Boolean, hoặc nó có thể phức tạp, giống như một đối tượng đại diện cho một kết nối cơ sở dữ liệu. Một đối tượng có thể gán cho một biến.

greeting = 'Hello'
=> "Hello"

Quan hệ mô tả như hình: Trong Ruby, greeting được nói là đã tham chiếu đến một đối tượng String, giá trị của greetingobject_id của của đối tượng String có giá trị là Hello, và ta có thể thao tác với String "Hello" thông qua biến greeting.

>> greeting
=> "Hello"
>> greeting.object_id
=> 70101471431160

Thử gán greeting cho một biến khác

>> whazzup = greeting
=> "Hello"
>> greeting
=> "Hello"
>> whazzup
=> "Hello"
>> greeting.object_id
=> 70101471431160
>> whazzup.object_id
=> 70101471431160

Ta thấy cả hai biến greetingwhazzup đều tham chiếu đến một object, được mô tả như hình sau: Thực hiện một số thao tác trên hai biến và xem kết quả:

>> greeting.upcase!
=> "HELLO"
>> greeting
=> "HELLO"
>> whazzup
=> "HELLO"
>> whazzup.concat('!')
=> "HELLO!"
>> greeting
=> "HELLO!"
>> whazzup
=> "HELLO!"
>> greeting.object_id
=> 70101471431160
>> whazzup.object_id
=> 70101471431160

Bây giờ ta sẽ gán một String khác cho greeting

>> greeting = 'Dude!'
=> "Dude!"
>> puts greeting
=> "Dude!"
>> puts whazzup
=> "HELLO!"
>> greeting.object_id
=> 70101479528400
>> whazzup.object_id
=> 70101471431160

Ta thấy greetingwhazzup không còn cùng tham chiếu đến một đối tượng nữa Vậy việc gán một đối tượng mới cho biến greeting không làm thay đổi giá trị của đối tượng mà greeting đã tham chiếu đến, chỉ đơn thuần là ngắt kết nối đến đối tượng cũ.

Mutability (Tính có thể thay đổi)

Chúng ta đã hiểu cách một biến tham chiếu đến một đối tượng, giờ cùng tìm hiều mutability (tính có thể thay đổi). Một số đối tượng có thể thay đổi(mutable) và một số không thể thay đổi(immutable). Ở những ngôn ngữ khác nhau thực hiện những việc khác nhau. Ví dụ trong C++, những đối tượng string là mutable, nhưng trong Java là immutable. Hiểu tính có thể thay đổi là cần thiết để hiểu cách các ngôn ngữ của bạn xử lý các object của nó như nào.

Immutable object trong Ruby

Trong Ruby, numbers và boolean là không thể thay đổi. Khi một đối tượng immutable được gán một giá trị, nó không thể thay đổi. Khi bạn gán một object mới cho 1 biến thì tham chiếu cũ đã không còn thay vào đó là một tham chiếu mới đến object mới. Object cũ đã ngắt kết nối, điều này có nghĩa rằng nó có thể sẽ được trình thu dọn rác(garbage collection) thu thập và giải phóng.

>> number = 3
=> 3
>> number
=> 3
>> number = 6
=> 6
>> number
=> 6

Hãy chứng minh điều này trên irb

>> a = 5.2
=> 5.2
>> b = a
=> 5.2
>> a.object_id
=> 46837436124653162
>> b.object_id
=> 46837436124653162
>> a += 1.1
=> 6.3
>> b
=> 5.2
>> a.object_id
=> 56745355304868258
>> b.object_id
=> 46837436124653162

Ban đầu, gán a = 5.2b = a, ta thấy a và b cùng tham chiếu đến một đối tượng, sau đó ta thực hiện cộng 1.1 vào a thì ta thấy a đã tham chiếu đến một đối tượng khác, còn b vẫn giữ tham chiếu ban đầu. Ta có thể tạo một class là immutable bằng cách không tạo các hàm để có thể thay đổi giá trị các thể hiện của nó.

Mutable objects

Không giống như các đối tượng numbers, booleans và một vài kiểu khác, hầu hết các objects trong Ruby là có thể thay đổi, chúng thuộc các class mà có các phương thức giúp thay đổi trạng thái giá trị thuộc tính của class đó. Lấy ví dụ với mảng, ta có thể dễ dàng thay đổi giá trị của một phần từ bằng cách gán một giá trị mới cho phần tử đó.

>> a = %w(a b c)
=> ["a", "b", "c"]
>> a.object_id
=> 70227178642840
>> a[1] = '-'
=> "-"
>> a
=> ["a", "-", "c"]
>> a.object_id
=> 70227178642840

Ví dụ trên chứng minh rằng ta có thể thay đổi giá trị của a, nhưng không tạo một đối tượng mới. Ta thấy a tham chiếu đến một Array, trong ví dụ trên, array chứa 3 phần tử, mỗi phần tử tham chiếu đến một đối tượng String. Do đó, khi ta gán ''-" cho a[1] thì không thay đổi a. String hay những classes collection có các hành vi trương tự nhau, một biến tham chiếu đến một collection(hay String), các phần tử của collection tham chiếu đến từng phần tử riêng của nó. Các phương thức delete, fill, insert sẽ thay đổi giá trị object gốc.

Lưu ý

Có ví dụ sau:

>> str = "hello"
=> "hello"
>> str.object_id
=> 14388680
>> str << "world"
=> "helloworl"
>> str.object_id
=> 14388680
>> str += "vietnam"
=>helloworldvietnam"
>> str.object_id
=> 14286340

Ta thực hiện phép cộng chuỗi, khi sử dụng toán tử += thì str sẽ tham chiếu đến một object mới, còn toán tử << sẽ thay đổi giá trị object ban đầu.

Áp dụng

Ví dụ 1: Xây dựng hàm tăng giá trị một số lên 1 đơn vị

def increment(a)
  a = a + 1
end
b = 3
puts increment(b)    #  4
puts b               #  3

Ví dụ 2: Xây dựng hàm cộng một chuỗi với một ký tự "*" mà không tạo một đối tượng mới

def append(s)
  s << '*'
end

t = 'abc'
puts append(t)    #  abc*
puts t            #  abc*

Kết luận

Ta thấy một biến trong Ruby đơn thuần là một tham chiếu đến một đối tượng bằng cách lưu object_id của đối tượng đó, và một biến đơn thuần là tên gọi của một object. Nhiều biến có thể cùng tham chiếu đến một đối tượng, do đó khi thay đổi một object thông qua biến thì sẽ đồng thời phản chiếu đến các biến khác. Chúng ta cũng đã tìm hiểu một số kiểu đối tượng mà có thể thay đổi giá trị của đối tượng(mutable) hoặc không thể thay đổi giá trị(immutable). Nếu bạn cố gắng thay đổi một đối tượng immutable bạn sẽ không thành công. Cuối cùng chúng ta đã tìm hiểu một chút về thế nào là truyền tham chiếu và tham trị thông qua phần áp dụng. Trong phần tiếp theo chúng ta sẽ tìm hiểu về Mutating và Non-Mutating methods. Hi vọng bài viết bổ ích với bạn 😄

Tài liệu tham khảo

Variable References and Mutability of Ruby Objects Mutable and Immutable Objects


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í