透鏡人

透鏡人

中等

說明

使用 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。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Roc Exercism

準備好開始 透鏡人 了嗎?

註冊 Exercism,透過 125 個練習 和真人引導來學習並精通 Roc,全部免費。