Search code examples
scalafunctional-programming

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.)


Solution

  • 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.