iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
0
Modern Web

續說 Ruby on Rails系列 第 7

[Day 7] Ruby destructive vs. non-destructive method

destructive method 指的是那些會改變物件(method 的 receiver)方法,也就是這方法是拿同一塊記憶體來用。
non-destructive method 則相反,需要重新要一塊記憶體,而不會去改原本那塊記憶體的內容。
關於記憶體位置跟object_id的關係,昨天的文章 Ruby 記憶體管理 GC vs. ObjectSpace有詳細介紹囉

這邊就稍微介紹 object id

object id

x = 2
x.object_id #=> 5 

x += 4
x #=> 6
x.object_id #=> 13

任何物件都有屬於自己的object id,從上面的程式碼可以看到 x 原本是2,object_id 是5,加了4之後 object_id也變了。因為在Ruby 數字跟Symbol都是不可改變的,也就是你不能改變x的值,然後x的object_id還是一樣的。不過 Ruby 的 Array, Hash, String 則都是可以改變的。

Time.object_id        # class 也有 object_id
# => 70328999205860 

String

首先來看看什麼情況下String的object_id會變,何時又不變

.+

string = '道瓊今天暴跌'
p "string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"

string += '一千點QQ'
p "string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"

output:

"string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189815321160"
"string: 道瓊今天暴跌一千點QQ"
"後來 string 的 object_id是 70189815320560"

可以看到+=之後,string的 object_id 變了。
所以string的.+ 方法是 non-destructive method

.<<

string = '道瓊今天暴跌'
p "string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"

string << '一千點QQ'
p "string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"

output:

"string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189815810980"
"string: 道瓊今天暴跌一千點QQ"
"後來 string 的 object_id是 70189815810980"

object_id 沒變誒
所以 <<destructive method

在方法裡面組字串呢

.+

string = '道瓊今天暴跌'
p "本來 string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"

# string 必須傳入,Ruby 不像 JS,會去方法宣告式外面找變數
def dj_down_point(points, string) 
  string += "#{points}點QQ"
  p "方法中 string: #{string}"
  p "方法中 string 的 object_id是 #{string.object_id}"
end

dj_down_point(1000, string)
p "後來 string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"

output:

"本來 string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189766779080"
"方法中 string: 道瓊今天暴跌1000點QQ"
"方法中 string 的 object_id是 70189766770560"
"後來 string: 道瓊今天暴跌"
"後來 string 的 object_id是 70189766779080"

可以看到方法裡面string 的object_id跟外面的不同,因為 .+新建一個物件,導致後來的string並不如我們預期成為組完字串的樣子。

.<<

string = '道瓊今天暴跌'
p "本來 string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"

def dj_down_point(points, string)
  string << "#{points}點QQ"
  p "方法中 string: #{string}"
  p "方法中 string 的 object_id是 #{string.object_id}"
end

dj_down_point(1000, string)
p "後來 string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"

output:

"本來 string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189766749660"
"方法中 string: 道瓊今天暴跌1000點QQ"
"方法中 string 的 object_id是 70189766749660"
"後來 string: 道瓊今天暴跌1000點QQ"
"後來 string 的 object_id是 70189766749660"
 => true 

object_id都是一樣,這才是我們希望組成的字串

小結

如果沒有注意到object_id的變化,在使用方法可能產生意想不到的bug。

Array

sort

array = [4, 2, 3, 1, 5]

puts "原本的 array = #{array}"
puts "原本的 Object_id = #{array.object_id}"

array_after = array.sort
puts "後來的 array = #{array}"
puts "後來的 array_after = #{array_after}"
puts "後來的 Object_id = #{array.object_id}"

Output:

原本的 array = [4, 2, 3, 1, 5]
原本的 Object_id = 70189817907240
後來的 array = [4, 2, 3, 1, 5]
後來的 array_after = [1, 2, 3, 4, 5]
後來的 Object_id = 70189817907240

array並沒有改變

.sort!

array = [4, 2, 3, 1, 5]

puts "原本的 array = #{array}"
puts "原本的 Object_id = #{array.object_id}"

array_after = array.sort!
puts "後來的 array = #{array}"
puts "後來的 array_after = #{array_after}"
puts "後來的 Object_id = #{array.object_id}"

Output:

原本的 array = [4, 2, 3, 1, 5]
原本的 Object_id = 70189817878380
後來的 array = [1, 2, 3, 4, 5]
後來的 array_after = [1, 2, 3, 4, 5]
後來的 Object_id = 70189817878380

所以array內容被改變了,Object_id 還是 同一塊記憶體
=> sort! 是 destructive method

從api 文件可以看得出來
https://docs.ruby-lang.org/en/2.6.0/Array.html#method-i-sort

sort → new_ary
sort {|a, b| block} → new_ary

sort! → ary
sort! {|a, b| block} → ary

所以使用sort!,你可以不把回傳值接起來。

總結

還有其他方法,像是Hash的 mergenon-destructive methodmerge!destructive method
Ruby很貼心的在 destructive method加上驚嘆號!,提醒你這是會改變原本的物件的。

昨天的文章有提到,為了更有效率的使用記憶體,盡量重複使用同一個物件。所以為了程式執行的效率,不妨試著使用一些destructive method,尤其在迴圈之中,效能累績起來也是可觀的。

Yes! First week finish!

參考文件

  1. https://medium.com/dev-genius/ruby-destructive-vs-non-destructive-methods-70a5505a391a
  2. https://ruby-doc.org/core-2.4.1/Array.html#method-i-sort-21

上一篇
[Day 6] Ruby 記憶體管理 GC vs. ObjectSpace
下一篇
[Day 8] Rails TimeWithZone 跟 Date 比較問題
系列文
續說 Ruby on Rails10
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言