次の 2 つの es があるとしますcase class
。
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
そして次のPerson
クラスのインスタンス:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
更新したい場合は、次のzipCode
ようraj
にする必要があります。
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
ネストのレベルが増えると、さらに醜くなります。update-in
このようなネストされた構造を更新するためのよりクリーンな方法 (Clojure のようなもの) はありますか?
ベストアンサー1
レンズはこのような用途のために作られたのに、誰もレンズを追加しなかったのはおかしい。だから、ここCSの背景に関する論文です。ここScalaでのレンズの使用について簡単に触れたブログです。ここScalazのレンズ実装であり、ここそれを使用したコードがあり、驚くほどあなたの質問に似ています。そして、定型文を減らすために、ここはケースクラス用の Scalaz レンズを生成するプラグイン。
ボーナスポイントとして、ここはレンズに関するもう一つのSOの質問と紙トニー・モリス著。
レンズの大きな特徴は、構成可能であることです。最初は少し扱いにくいですが、使用すればするほど便利になります。また、個々のレンズをテストするだけでよく、レンズの構成を当然のこととして扱えるため、テスト性に優れています。
したがって、この回答の最後に記載されている実装に基づいて、レンズを使用してこれを行う方法は次のとおりです。まず、住所の郵便番号と人物の住所を変更するレンズを宣言します。
val addressZipCodeLens = Lens(
get = (_: Address).zipCode,
set = (addr: Address, zipCode: Int) => addr.copy(zipCode = zipCode))
val personAddressLens = Lens(
get = (_: Person).address,
set = (p: Person, addr: Address) => p.copy(address = addr))
次に、人の郵便番号を変更するレンズを取得するためにそれらを構成します。
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
最後に、そのレンズを使用して raj を変更します。
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
または、構文糖を使って次のようにします。
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
あるいは:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
以下は、この例で使用した Scalaz からの簡単な実装です。
case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
def apply(whole: A): B = get(whole)
def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
def mod(a: A, f: B => B) = set(a, f(this(a)))
def compose[C](that: Lens[C,A]) = Lens[C,B](
c => this(that(c)),
(c, b) => that.mod(c, set(_, b))
)
def andThen[C](that: Lens[B,C]) = that compose this
}