Это можно сделать с линзами . Сама идея линзы состоит в том, чтобы иметь возможность увеличивать конкретную часть неизменяемой структуры и иметь возможность 1) извлекать меньшую часть из более крупной структуры или 2) создавать новую более крупную структуру с измененной меньшей частью , В этом случае, что вы хотите, это # 2.
Во-первых, простая реализация Lens
, украденная у этот ответ , украденная из скаляза:
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]
. Мы указываем, на какую «меньшую часть» мы хотим взглянуть, предоставляя определенный ключ. (Вдохновленный тем, что я помню из презентации Эдварда Кметта о линзах в 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"
. В противном случае это решение оказывается очень похожим на решение adjust
, представленное Travis.