Idiomatic way to update value in a Map based on previous value

后端 未结 4 759
日久生厌
日久生厌 2020-12-08 00:35

Let\'s say I store bank accounts information in an immutable Map:

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


        
4条回答
  •  生来不讨喜
    2020-12-08 00:47

    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.

提交回复
热议问题