9

Monocle は、レンズ パターンを実装する優れたライブラリ (唯一のライブラリではありません) です。これは、巨大なネストされたオブジェクトの 1 つのフィールドを変更する必要がある場合に最適です。例のように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)

これは素晴らしいことです。私の知る限り、マクロ マジックはすべてを上記とまったく同じコードに変換します。

しかし、複数のアクションを組み合わせたい場合はどうすればよいでしょうか? 1回の電話で通りの名前、住所の都市、会社名を同時に変更したい場合はどうすればよいですか? 次のように:

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)

これは、最終的にONEだけでなく、 THREE employee.copy(...).copy(...).copy(...)呼び出しに変換されます。それをより良くする方法は? employee.copy(...)

さらに、一連の操作を適用することは本当に素晴らしいことです。Seq[(Lens[Employee, String], String => String)]最初の要素が正しいフィールドを指すレンズであり、2 番目の要素がそれを変更する関数であるペアのシーケンスと同様です。このような一連の操作を外部から構築するのに役立ちます。上記の例の場合:

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}
)

または似たようなもの...

4

1 に答える 1

11

私の知る限り、マクロ マジックはすべてを上記とまったく同じコードに変換します。

そうではありません。

この簡単なコード:

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)

また、3 つの冗長オブジェクト (匿名レンズ クラス、構文シュガー用の ApplyLens、および から返されるラムダmodify) が含まれています。capitalizeそして、 で構成する代わりに直接使用することで、さらにスキップしましたheadOption

いいえ、無料の夕食はありません。ただし、ほとんどの場合、これで十分であり、余分なレンズ オブジェクトや中間結果については誰も気にしません。


複数の操作

タイプが一致する場合、複数のレンズからトラバーサル (コレクション レンズ) を構築できます (ここでEmployeeString) 。

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効率のために、 et alを使用できます。手動で記述する必要がありますcopy(そして、私は今それを行うのが面倒です)。

最後に、さまざまな変換をさまざまなタイプのフィールドに適用する場合は、その事実を使用してtype の関数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) 

* - これはdesugarAmmonite-REPL からの単純化された出力です。私はスキップしmodifyFました、ところで

于 2017-08-16T14:03:01.873 に答える