简体   繁体   中英

In Scala Functional Programming, is there an idiomatic way to map with a state?

A conventional mapping function has the signature A => B , to transform a F[A] into an F[B] , for example a List[A] into a List[B] .

But what if do you do if the mapping function should carry some state along which is required for the computation of B ?

Say, the mapping function looks like this: (A, S) => (B, S) , where S is the type of the State . For each A , the previously returned S is passed into the mapping function, while initially a zero element is provided for the state. The mapping function then returns a new state (along with the result) which is then again passed along with the next value, and so forth.

Of course, .map is not powerful enough to do this, so the solution must be based on another operator.

For illustrative purposes, to give a concrete example, say I have a sequence of Ints , and I want calculate the difference of each Int to the previous Int in that sequence. The implementation of the mapping function as described above would look like this:

  def mapping(currentElement: Int, previousElement: Option[Int]): (Option[Int], Option[Int]) = {
    (previousElement.map(currentElement - _), Some(currentElement))
  }

The initial zero value for previousElement would be None , and after the first element it would always be Some(currentElement) . The result for each iteration would be a Some of the current value minus the last value, except for the first element, where it is None .

How could I transform, for example List(1, 4, 3) to List(None, Some(3), Some(-1)) Using the mapping function?

(Please note that the Int-subtraction example is purely for illustrative purposes, and the question's focus is a universal solution for the described type of operation.)

There are some libraries available to do "mtl-style" state passing which is what you are describing. (Check the type signatures after this snippet)

import cats._
import cats.data._
import cats.implicits._

// Given an element and state, calculate next state and return value
def modifyEntry(currentElement: Int): State[Option[Int], Option[Int]] = for {
  previousElement <- State.get
  _ <- State.set[Option[Int]](Some(currentElement)) // Next State
} yield previousElement map (currentElement - _) // Calculated Value

// It is useful for hiding state and passing it implicitly
val result =
  for {
    val1 <- modifyEntry(1)
    val2 <- modifyEntry(2)
    val3 <- modifyEntry(3)
  // Final state is implicittly stored in the yielded State[Option[Int], Seq[Option[Int]]]
  } yield Seq(val1, val2, val3) 

// Run with None initial State and coerce evaluation (cats is lazy by default)
println("for-comprehension result (final state and value): " -> result.run(None).value)

// More importantly, it is _easy_ to compose with Traversables or other generic cats traits
println("traverse result (only value): " ->
  List(1,2,3).traverse(modifyEntry).runA(None).value) // List(None, Some(1), Some(1))
println("traverse result (only value): " ->
  List(1,4,3).traverse(modifyEntry).runA(None).value) // List(None, Some(3), Some(-1))

You would be particularly interested in the StateFunctions trait from scalaz or State from cats . Comparison: https://github.com/fosskers/scalaz-and-cats

Scalaz StateFunctions:

trait StateFunctions extends IndexedStateFunctions {
  // ...
  def get[S]: State[S, S] = State(s => (s, s))

  def put[S](s: S): State[S, Unit] = State(_ => (s, ()))
  // ...
}

Cats StateFunctions with some modifications:

abstract private[data] class StateFunctions {
  // ...
  def get[S]: State[S, S] = ??? // Some other code like State(s => (s, s))

  def set[S](s: S): State[S, Unit] = State(_ => (s, ()))
}

For cats, check the excellent docs with some other examples: https://typelevel.org/cats/datatypes/state.html

For scalaz, here is a good talk with an overview of "mtl-style" in Scala and Scalaz: Paweł Szulc - GETTING MORE MILEAGE FROM YOUR MONADS WITH MTL , though beware of the

For either of them, beware of the cons of MonadTransformers (not mtl-style/traits, see the second part): http://degoes.net/articles/effects-without-transformers

The Scala 2.13.x unfold() method maintains a State that is carried forward similar to your example.

List.unfold((Option.empty[Int], List(1, 4, 3))){
  case (prev, hd::tl) => Some((prev.map(hd.-), (Some(hd),tl)))
  case (prev, Nil)    => None
}
//res0: List[Option[Int]] = List(None, Some(3), Some(-1))

This is available on LazyList and Iterator so it can used to create a pseudo-infinite stream.

The "operator" you are looking for is fold :

List(1, 4, 3).foldLeft(None: Option[Int], List[Option[Int]]())
   ((acc, curr) => (Some(curr), acc._1.map(_ - curr) :: acc._2))
    ._2
    .reverse

Another way to think about is using zip :

val xs = List(1, 4, 3)

val result = None :: xs.zip(xs.drop(1)).map(currAndNext => Some(currAndNext._2 - currAndNext._1))

What about this?

  implicit class RichTraversableLike[A, Repr](coll: TraversableLike[A, Repr]) {
    def mapWithState[S, B, That](initial: S)(op: (S, A) => (S, B))(implicit bf: CanBuildFrom[Repr, B, That]): (S, That) = {
      var state: S = initial
      val mapped = coll.map { x: A =>
        val (newState, mappedX) = op(state, x)
        state = newState
        mappedX
      }
      state -> mapped
    }
  }

For your dummy example, it would look like this:

    List(1, 4, 3).mapWithState(Option.empty[Int]) {
      case (previousElement: Option[Int], currentElement: Int) =>
        Some(currentElement) -> previousElement.map(currentElement - _)
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM