这可以通过镜头来完成。镜头的真正想法是能够放大不可变结构的特定部分,并且能够 1)从较大结构中检索较小部分,或 2)使用修改后的较小部分创建新的较大结构.在这种情况下,您想要的是 #2。
首先,一个简单的Lens实现,从this answer盗取,从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
}
接下来,一个智能构造函数创建一个从“较大结构”Map[A,B] 到“较小部分”Option[B] 的镜头。我们通过提供一个特定的键来指示我们想要查看哪个“较小的部分”。 (灵感来自我对Edward Kmett's presentation on Lenses in Scala 的记忆):
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))
n.b.实际上,我从我偷的答案中更改了mod,以便它接受它的输入咖喱。这有助于避免额外的类型注释。还要注意_.map,因为请记住,我们的镜头是从Map[A,B] 到Option[B]。这意味着如果地图不包含密钥"Mark",则地图将保持不变。否则,这个解决方案最终会与 Travis 提出的adjust 解决方案非常相似。