基于以前的值更新地图中的值的习惯方法
比方说,我将银行账户信息存储在一个不可变的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)
|+|
运算符将总结现有密钥的值,或将值插入新密钥。