簡體   English   中英

結合鏡片系列

[英]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 (我現在懶得做)。

最后,如果要將各種轉換應用於不同類型的字段,則應該使用modifyset返回類型為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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM