2016-09-01 3 views
2

Scalaで入れ子にされた不変の構造を更新するためのよりクリーンな方法を見つけようとしています。私はClojureでassoc-inに似た何かを探していると思います。私はどのくらいのタイプがこれを考慮するかはわかりません。例えば入れ子にされた不変のマップを更新する方法

は、Clojureの中で、私がしたい、ネストされたマップの「市」の属性更新する:

> (def person {:name "john", :dob "1990-01-01", :home-address {:city "norfolk", :state "VA"}}) 
#'user/person 
> (assoc-in person [:home-address :city] "richmond") 
{:name "john", :dob "1990-01-01", :home-address {:state "VA", :city "richmond"}} 

をScalaで私のオプションは何ですか?

val person = Map("name" -> "john", "dob" -> "1990-01-01", 
      "home-address" -> Map("city" -> "norfolk", "state" -> "VA")) 

答えて

4

Scalaは静的型付け言語であるので、あなたが最初に離れて任意の文字列-to-anyの文字列から移動することによって、あなたのコードの安全性を増やすことをお勧めします。

case class Address(city: String, state: String) 
case class Person(name: String, dob: java.util.Date, homeAddress: Address) 

(はい、java.util.Dateためbetter alternativesがあります)。

その後、あなたはこのようなアップデートを作成:このネストされたcopyを避けるために

val person = Person(name = "john", dob = new java.util.Date(90, 0, 1), 
    homeAddress = Address(city = "norfolk", state = "VA")) 

person.copy(homeAddress = person.homeAddress.copy(city = "richmond")) 

を、あなたは(他の多くがあります)MonocleまたはQuicklensのように、レンズのライブラリを使用します。

import com.softwaremill.quicklens._ 
person.modify(_.homeAddress.city).setTo("richmond") 
+2

特に、 'Map [String、Any]'はScalaでは面白くないでしょう。 – Ryan

+0

@ Ryan:任意のコレクションタイプシグネチャの「Any」の悪いサインが存在していませんか? List [Any]など – Samar

+0

はい、タイプシステムが「Any」を推論しなければならないときは、おそらく間違いを犯したでしょう。 – Ryan

2

他の回答で示したように、あなたはクリーン取得するcase classesを活用することができますが、データ・オブジェクトを入力しました。しかし、場合にはあなたが必要なものマップを更新するだけです:

val m = Map("A" -> 1, "B" -> 2) 
val m2 = m + ("A" -> 3) 

結果(ワークシート):Map+オペレータは、新しいキーと値のペアを追加します

m: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2) 
m2: scala.collection.immutable.Map[String,Int] = Map(A -> 3, B -> 2) 

、すでに存在する場合は上書きします。ただし、元の値がvalであるため、元の値を変更できないため、結果に新しいvalを割り当てる必要があります。

、あなたの例では、あなたがこれを手動でやや面倒になりやって、ネストされた値を書き換えているので:これは、いくつかの損失のデータを生成する

val m = Map("A" -> 1, "B" -> Map("X" -> 2, "Y" -> 4)) 
val m2 = m + ("B" -> Map("X" -> 3)) 

(ネストY値が消える):

したがって
m: scala.collection.immutable.Map[String,Any] = Map(A -> 1, B -> Map(X -> 2, Y -> 4)) 
m2: scala.collection.immutable.Map[String,Any] = Map(A -> 1, B -> Map(X -> 3)) // Note that 'Y' has gone away. 

、元の値をコピーし、それをバックに再割り当てすることができ強制:

val m = Map("A" -> 1, "B" -> Map("X" -> 2, "Y" -> 4)) 
val b = m.get("B") match { 
    case Some(b: Map[String, Any]) => b + ("X" -> 3) // Will update `X` while keeping other key-value pairs 
    case None => Map("X" -> 3) 
} 
val m2 = m + ("B" -> b) 

これは「期待」の結果が得られますが、明らかに多くのコードです:要するに

m: scala.collection.immutable.Map[String,Any] = Map(A -> 1, B -> Map(X -> 2, Y -> 4)) 
b: scala.collection.immutable.Map[String,Any] = Map(X -> 3, Y -> 4) 
m2: scala.collection.immutable.Map[String,Any] = Map(A -> 1, B -> Map(X -> 3, Y -> 4)) 

、任意の不変のデータ構造と、それはあなたが本当にあなたが望むすべての部分をコピーしているときに、「更新」と必要に応じて更新された値を含めます。構造が複雑な場合、これは厄介なものになる可能性があります。したがって、@ 0 ___が与えた、例えば、Monocleの勧告。

+0

ネストされた例で「データの損失」がどのように起こっているのか説明できますか? – Samar

+0

@Samar答えを更新しました。具体的には、ネストされたマップの 'Y'が消えます。 –

+0

Y値を維持するために、val m2 = m +( "B" - > Map( "X" - > 3、 "Y" - > 4))を実行できます。 – Samar

0

他の2つの答えは、あなたの問題を正しくモデリングすることの重要性を合理的にまとめているので、Map [String、Object]型のコレクションに対処する必要はありません。

スララの静かな強力な関数パイプライニング機能と高次関数機能を利用して、私の2セントをここに追加するだけです。マップ値が異なる型のため、MapのシグネチャをMap [String、Any]として扱うので、醜いasInstanceOfキャストが必要です。

val person: Map[String,Any] = Map("name" -> "john", "dob" -> "1990-01-01", "home-address" -> Map("city" -> "norfolk", "state" -> "VA")) 

val newperson = person.map({case(k,v) => if(k == "home-address") v.asInstanceOf[Map[String,String]].updated("city","Virginia") else k -> v}) 
関連する問題