在 Julia 中存取物件的欄位的時候,設計者會想要對於存取的過程中加上一些檢查,這個想法是來自於傳統物件導向 getter/setter 的概念。

例如,在自己設計的物件當中有兩個矩陣,這兩個矩陣可以讓使用者隨意更新,但是需要保證這兩個矩陣的維度是一致的。這個時候就需要在使用者更新矩陣的時候加入檢查,以確保兩個矩陣之間的維度是一致的,如果檢查不通過,可能會跳例外或者是不讓矩陣被更新。

那如果要在 Julia 中,存取物件時加入檢查的程式碼,要怎麼辦呢?

Julia 提供了兩個 setter 來提供設定物件的欄位:setproperty!setfield!

setfield! 是屬於內部的 API,這個不能覆寫,所以我們要用的是 setproperty!

setproperty! 的 API 可以藉由查詢文件得知:

1
setproperty!(value, name::Symbol, x)

也就是當你在呼叫 a.b = c 其實就會去呼叫 setproperty!(a, :b, c)

依據上述的例子,我們可以示範以下程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mutable struct Foo
A::Matrix
B::Matrix
end

function setproperty!(obj::Foo, name::Symbol, x)
if name == :A
if size(obj.B) == size(x)
setfield!(obj, name, x)
else
throw(DimensionMismatch())
end
elseif name == :B
if size(obj.A) == size(x)
setfield!(obj, name, x)
else
throw(DimensionMismatch())
end
else
setfield!(obj, name, x)
end
end

這邊有幾點需要特別注意,第一是語言中的 setproperty! 第一個參數是 Any 型別,所以第一個參數一定要指定自己的型別是什麼,不然會跟語言本身的衝突。

第二個參數吃進來的是 Symbol 型別,這個也要指定,不然會發生 ambiguous 的狀況。

第三個參數為了廣義,所以是 Any 型別。

函式裡頭要區分開不同的欄位,這邊用 if-else 處理。想要用多重分派的機制處理的也是可以,但會比較麻煩,而且用到 Val() 會有效能降低的現象。

通過檢查後,要真正設定物件欄位,這邊用 setfield!(obj, name, x) 是比較好的作法。如果呼叫 obj.A = x,則會去呼叫 setproperty!(obj, :A, x),就會變成無限遞迴呼叫了,所以 =setproperty! 在這邊都沒辦法用。

最後,我們把要檢查的欄位都處理好之後,我們不想要動到其他的欄位設定的行為,那就讓其他的欄位都用 setfield!(obj, name, x) 處理掉吧!