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.