This could be done with lenses. The very idea of a lens is to be able to zoom in on a particular part of an immutable structure, and be able to 1) retrieve the smaller part from a larger structure, or 2) create a new larger structure with a modified smaller part. In this case, what you desire is #2.
Firstly, a simple implementation of Lens
, stolen from this answer, stolen from 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
}
Next, a smart constructor to create a lens from "larger structure" Map[A,B]
to "smaller part" Option[B]
. We indicate which "smaller part" we want to look at by providing a particular key. (Inspired by what I remember from 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)
}
)
Now your code can be written:
val m2 = containsKey("Mark").mod(m)(_.map(_ - 50))
n.b. I actually changed mod
from the answer I stole it from so that it takes its inputs curried. This helps to avoid extra type annotations. Also notice _.map
, because remember, our lens is from Map[A,B]
to Option[B]
. This means the map will be unchanged if it does not contain the key "Mark"
. Otherwise, this solution ends up being very similar to the adjust
solution presented by Travis.
map.get(k).fold(map)(b => map.updated(k, f(b)))
if you want to ignore a missing key, or an approach that hasf: Option[B] => B
if you want to be able to set the key in its absence. – Levenson