Search code examples
scalafunctional-programming

Maybe functor with results accumulation


I'd like to implement something like a functor for Option which accumulates successful results.

It should behave like this (scala-like code below):

Some(1).map(_ + 1) # => Some[(Int, Int)] = Some((1,2))
Some(1).map(_ + 1).map(_ * 3) # => Some[(Int, Int, Int)] = Some((1,2,6))
Some(1).map(_ => None) # Option[None.type] = Some(None)

I do understand this is not a proper functor because it does not preserve identity:

Some(42).map(identity) # => Some[(Int, Int]) = Some((42, 42))

To add some context, I thought it would be useful when there is a chain of maps:

val result = Some(42).map(compute_second_value).map(compute_third_value)

and a pattern matching on result which depends not only on result final value:

result match {
  case Some(value) => do_something_with_all_results(result._1, result._2, result._3)
  case None => ...
}

So I'm looking for a well-known solution / approach / theoretical term that is related to desired behaviour


Solution

  • You could implement it in a "general" way with a foldLeft in Scala and working on a sequence rather than a tuple:

    val ops: Seq[Int => Int] = Seq(computeSecondValue, computeThirdValue)
    
    ops.foldLeft(Seq(1)) { case (acc, op) =>
      acc :+ op(acc.last)
    }
    // Seq[Int]
    // Seq(1, computeSecondValue(1), computeThirdValue(computeSecondValue(1)))
    

    Or in a less general approach and keeping tuples:

    for {
      first <- Some(1)
      second = computeSecondValue(first)
      third = computeThirdValue(second)
    } yield (first, second, third)
    // Option[Tuple3[Int, Int, Int]]
    

    In both cases, I didn't manage the flatMap case if operations return an Option but this can be implemented relatively easily by the reader.