[英]Combine collection of lenses
Monocle是一個很棒的庫(而不是唯一一個)實現鏡頭模式的庫,如果我們必須在巨大的嵌套對象中更改一個字段,這是很棒的。 例如http://julien-truffaut.github.io/Monocle/
case class Street(number: Int, name: String)
case class Address(city: String, street: Street)
case class Company(name: String, address: Address)
case class Employee(name: String, company: Company)
以下樣板
employee.copy(
company = employee.company.copy(
address = employee.company.address.copy(
street = employee.company.address.street.copy(
name = employee.company.address.street.name.capitalize // luckily capitalize exists
)
)
)
)
可以輕松更換
import monocle.macros.syntax.lens._
employee
.lens(_.company.address.street.name)
.composeOptional(headOption)
.modify(_.toUpper)
哪個好。 據我所知,宏魔術將所有內容完全轉換為與上面相同的代碼。
但是,如果我想結合幾個動作怎么辦? 如果我想通過一次通話同時更改街道名稱,地址城市和公司名稱怎么辦? 如下:
employee.copy(
company = employee.company.copy(
address = employee.company.address.copy(
street = employee.company.address.street.copy(
name = employee.company.address.street.name.capitalize // luckily capitalize exists
),
city = employee.company.address.city.capitalize
),
name = employee.company.name.capitalize
)
)
如果我只是在這里重復使用鏡頭,我會有以下代碼:
employee
.lens(_.company.address.street.name).composeOptional(headOption).modify(_.toUpper)
.lens(_.company.address.city).composeOptional(headOption).modify(_.toUpper)
.lens(_.company.name).composeOptional(headOption).modify(_.toUpper)
最終將轉換為THREE employee.copy(...).copy(...).copy(...)
invocations,而不僅僅是一個 employee.copy(...)
。 如何讓它變得更好?
此外,應用一系列操作真的很棒。 像Seq[(Lens[Employee, String], String => String)]
序列一樣Seq[(Lens[Employee, String], String => String)]
其中第一個元素是指向正確字段的鏡頭,第二個元素是修改它的函數。 它將有助於從外部構建這樣的操作序列。 對於上面的例子:
val operations = Seq(
GenLens[Employee](_.company.address.street.name) -> {s: String => s.capitalize},
GenLens[Employee](_.company.address.city) -> {s: String => s.capitalize},
GenLens[Employee](_.company.name) -> {s: String => s.capitalize}
)
或類似的東西......
據我所知,宏魔術將所有內容完全轉換為與上面相同的代碼。
它沒有。
這個簡單的代碼:
employee.lens(_.name)
.modify(_.capitalize)
變得像怪物那樣的東西*:
monocle.syntax.ApplyLens(employee,
new monocle.PLens[Employee, Employee, String, String] {
def get(e: Employee): String = e.name;
def set(s: String): Employee => Employee = _.copy(s);
def modify(f: String => String): Employee => Employee = e => e.copy(f(e.name))
}
}).modify(_.capitalize)
這與簡單相去甚遠
employee.copy(name = employee.name.capitalize)
並包括三個冗余對象(匿名鏡頭類,ApplyLens用於語法糖和lambda從modify
返回)。 我們通過直接使用capitalize
而不是使用headOption
進行編寫來跳過更多。
所以不,沒有免費的晚餐。 然而,大多數情況下,它足夠好,沒有人關心額外的鏡頭對象和中間結果。
如果它們的類型對齊,你可以從多個鏡頭構建一個Traversal(聚合鏡頭)(這里是Employee
to String
)
val capitalizeAllFields = Traversal.applyN(
GenLens[Employee](_.name),
GenLens[Employee](_.company.address.street.name),
GenLens[Employee](_.company.address.city),
GenLens[Employee](_.company.name)
).modify(_.capitalize)
這仍然會多次調用copy
。 為了提高效率,您可以使用Traversal.apply4
等。 品種,這將要求你手動編寫該copy
(我現在懶得做)。
最后,如果要將各種轉換應用於不同類型的字段,則應該使用modify
和set
返回類型為Employee => Employee
的函數的事實。 對於你的例子,那將是:
val operations = Seq(
GenLens[Employee](_.company.address.street.name).modify(_.capitalize),
GenLens[Employee](_.company.address.street.number).modify(_ + 42),
GenLens[Employee](_.company.name).set("No Company Inc.")
)
val modifyAll = Function.chain(operations)
// does all above operations of course, with two extra copy calls
modifyAll(employee)
* - 這是Ammonite-REPL中desugar
簡化輸出。 我跳過了modifyF
,順便說一句
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.