基于以前的值更新地图中的值的习惯方法

比方说,我将银行账户信息存储在一个不可变的Map

 val m = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65) 

我想从马克的账户里拿出50美元。 我可以这样做:

 val m2 = m + ("Mark" -> (m("Mark") - 50)) 

但是这个代码对我来说似乎很难看。 有没有更好的方法来写这个?

不幸的是, Map API没有adjust 。 我有时使用类似下面的函数(模拟Haskell的Data.Map.adjust ,具有不同的参数顺序):

 def adjust[A, B](m: Map[A, B], k: A)(f: B => B) = m.updated(k, f(m(k))) 

现在adjust(m, "Mark")(_ - 50)做你想要的。 你也可以使用pimp-my-library模式来得到更自然的m.adjust("Mark")(_ - 50)语法,如果你真的想要更干净的东西的话。

(请注意,如果k不在地图中,上面的短版本会引发exception,这与Haskell行为不同,可能是您想要在实际代码中修复的东西。)

这可以通过镜头来完成。 镜头的想法是能够放大不可变结构的特定部分,并能够从较大的结构中检索较小的部分,或者2)创build具有修改的较小部分的新的较大结构。 在这种情况下,你想要的是#2。

首先,从这个答案中偷了一个简单的Lens实现,从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 } 

接下来,一个聪明的构造函数来创build一个镜头,从“大结构” Map[A,B]到“小部分” Option[B] 。 我们通过提供一个特定的密钥来表明我们想要看哪个“小部分”。 (从爱德华·科梅特(Edward Kmett)关于斯卡拉镜头的介绍中得到启发):

 def containsKey[A,B](k: A) = Lens[Map[A,B], Option[B]]( get = (m:Map[A,B]) => m.get(k), set = (m:Map[A,B], opt: Option[B]) => opt match { case None => m - k case Some(v) => m + (k -> v) } ) 

现在你的代码可以写成:

 val m2 = containsKey("Mark").mod(m)(_.map(_ - 50)) 

NB我实际上改变了从我偷了它的答案,所以它的inputcurry。 这有助于避免额外的types注释。 另外请注意_.map ,因为请记住,我们的镜头是从Map[A,B]Option[B] 。 这意味着如果地图不包含"Mark"键,则地图将保持不变。 否则,这个解决scheme最终会与Travis提供的adjust解决scheme非常相似。

一个SO答案提出了另一个select,使用|+| 来自scalaz的运算符

 val m2 = m |+| Map("Mark" -> -50) 

|+| 运算符将总结现有密钥的值,或将值插入新密钥。