使用 lens 來更新巢狀記錄(僅限於具有不可變資料的語言)。
更新巢狀且不可變的記錄裡的欄位,實在有點煩人。 這類情況的程式碼有多繁瑣,取決於結構有多深。 假設你有一個 Person,裡面包含一個 Address,Address 有一個 Street,Street 又有一個 Number;那麼要更新 Number,就得先用新的 Number 建立一個新的 Street,再用新的 Street 建立新的 Address,最後用新的 Address 建立新的 Person。 已經搞混了嗎?
這個問題的其中一個解法,就是使用 lenses。
用 lenses 實作幾個存取記錄的函式。 測試套件也允許你完全避開 lenses,讓你可以試試不同的做法。
在 Roc 中,lens 並不常用,因為記錄更新語法就能漂亮地涵蓋大多數使用情境。舉例來說,看看這個巢狀記錄:
bob = { hair: { color: Brown, length: Long }, mouth: Smiling, height: 6.0 }
現在來幫 Bob 剪個頭髮吧:
new_bob = { ..bob, hair: { ..bob.hair, length: Short } }
完全不需要 lens!話說回來,雖然 lens 在 Roc 裡很罕見,但在 Haskell、Scala 等其他語言中卻很常見,所以你可能會好奇該怎麼在 Roc 裡實作它們。這也是練習一些進階 Roc 功能的好方法,例如參數化型別,以及包含函式的記錄。其實,以下就是在 Roc 裡表示 lens 的一種方式:
Lens(whole, part) := {
get : whole -> part,
set : whole, part -> whole,
}
這是參數化型別:whole和part可以是任何型別。whole通常是記錄,而part則可以是任何欄位型別。get欄位包含一個函式,會回傳指定whole中的特定part;set欄位則包含一個函式,會回傳指定whole的副本,只是把特定的part換成給定的part。
舉例來說,以下是取得或設定某人頭髮的 lens:
hair_lens = {
get: |person| person.hair,
set: |person, hair| { ..person, hair },
}
我們可以用這個 lens 把焦點放在 Bob 的頭髮上,就像這樣:
bob_hair = bob |> hair_lens.get
也可以像這樣改變他的頭髮:
new_hair = { ..bob.hair, length: Short }
new_bob = bob |> (hair_lens.set)(new_hair)
注意:hair_lens.set外面的括號是必要的:少了它們,Roc 會去hair_lens的型別裡尋找一個名為set的函式。
當你開始組合 lens 時,它們會變得特別有用,讓你取得和設定深度巢狀的部分。這可以透過實作像這樣的函式來達成:
compose : Lens(outer, inner), Lens(inner, part) -> Lens(outer, part)
這個函式實作好之後,你就可以用它建立像這樣的頭髮長度 lens:
length_lens = {
get: |hair| hair.length,
set: |hair, length| { ..hair, length },
}
hair_length_lens = compose(hair_lens, length_lens)
現在你可以再幫 Bob 剪一次頭髮:
set_hair_length = hair_length_lens.set
new_bob = bob |> set_hair_length(Short)
你可能也會想實作一個函式,讓你能對巢狀的部分套用任何轉換:
over : Lens(whole, part), whole, (part -> part) -> whole
實作好之後,你可以像這樣使用over:
cut_hair = |current_length| match current_length {
Long => Short
Short => Bald
Bald => Bald
}
new_bob = hair_length_lens |> over(bob, cut_hair)
在這個練習中,測試會檢查你的程式碼是否能修改以下巢狀記錄的各種巢狀部分:
test_person : Person
test_person = {
name: { forenames: "Jane Joanna", surname: "Doe" },
birth: {
born_at: { street: "Longway", house_number: 1024, place: "Springfield", country: "United States" },
born_on: { year: 1984, month: 4, day: 12 },
},
address: { street: "Shortlane", house_number: 2, place: "Fallmeadow", country: "Canada" },
}
我們建議你先用 Roc 的記錄更新語法通過測試,然後再試著改用 lens。