简体   繁体   中英

Functions composition, which accumulates intermediate results

Suppose I have a few functions Int => Option[Int] :

val f1: Int => Option[Int] = x => if (x < 10) Some(x + 1) else None
val f2: Int => Option[Int] = x => if (x < 10) Some(x + 2) else None
val f3: Int => Option[Int] = x => if (x < 10) Some(x + 3) else None

Now I would like to compose them and make a new function, which accumulates the intermediate results, ie the results of f1 , f2 , and f3 .

So I add a new class Accumulator :

class Accumulator(x: Int) {
  val ox1 = f1(x)
  val ox2 = ox1.flatMap(f2)
  val ox3 = ox2.flatMap(f3)
  def apply() = ox3
}

val f = {x => new Accumulator(x)}

Now I can see all intermediate results of the computation:

scala> f(0)
res18: X = $$$a5cddfc4633c5dd8aa603ddc4f9aad5$$$$w$X@10596df6

scala> res18.ox1
res19: Option[Int] = Some(1)

scala> res18.ox2
res20: Option[Int] = Some(3)

scala> res18()
res21: Option[Int] = Some(6)

I do not like this approach because it requires a new class for every computation. Could you suggest another approach to write a function f composed from f1 , f2 , and f3 that return also the intermediate results, ie the results of f1 , f2 , and f3 calls.

Why not use a foldLeft with a list of functions?

def accumulate(x: Int, funcs: List[Int => Option[Int]]): List[Option[Int]] = funcs.foldLeft(List[Option[Int]]()) {
  case (Nil, func) => List(func(x))
  case (res :: tail, func) => res.flatMap(func) :: res :: tail
}.reverse

val f1: Int => Option[Int] = x => if (x < 10) Some(x + 1) else None
val f2: Int => Option[Int] = x => if (x < 10) Some(x + 2) else None
val f3: Int => Option[Int] = x => if (x < 10) Some(x + 3) else None

accumulate(0, List(f1, f2, f3))

This gives List[Option[Int]] = List(Some(1), Some(3), Some(6)) .

EDIT:

As Marth pointed out, there's a dedicated function for this - scanLeft , however, I'd like to propose a different approach to using it. Make the initial value your input parameter instead of a function:

def accumulate(x: Int, funcs: List[Int => Option[Int]]): List[Option[Int]] =
  funcs.scanLeft(Option(x)) {
    case (acc, op) => acc.flatMap(op)
  }.tail

val f1: Int => Option[Int] = x => if (x < 10) Some(x + 1) else None
val f2: Int => Option[Int] = x => if (x < 10) Some(x + 2) else None
val f3: Int => Option[Int] = x => if (x < 10) Some(x + 3) else None

accumulate(0, List(f1, f2, f3))

You could use .scanLeft on a List of Function , which (from the docs):

Produces a collection containing cumulative results of applying the operator going left to right.

scala> val f1: Int => Option[Int] = x => if (x < 10) Some(x + 1) else None
f1: Int => Option[Int] = <function1>

scala> val f2: Int => Option[Int] = x => if (x < 10) Some(x + 2) else None
f2: Int => Option[Int] = <function1>

scala> val f3: Int => Option[Int] = x => if (x < 10) Some(x + 3) else None
f3: Int => Option[Int] = <function1>

scala> val fList = List(f1,f2,f3)
fList: List[Int => Option[Int]] = List(<function1>, <function1>, <function1>)

scala> val composed = fList.scanLeft((x:Int) => Option(x)) {
         case (composedFun, f) => (x:Int) => (composedFun(x)) flatMap f 
       }.tail
composedFunctions: List[Int => Option[Int]] = List(<function1>, <function1>, <function1>)

scala> composed.map(_(2))
res24: List[Option[Int]] = List(Some(3), Some(5), Some(8))

scala> composed.map(_(8))
res25: List[Option[Int]] = List(Some(9), Some(11), None)

Note that I had to introduce an initial value (z, here (x:Int) => Option(x) ).
You might want to write a function that takes a list of functions and uses funList.head as the initial value (and calls .scanLeft on funList.tail instead of funList ).

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