简体   繁体   中英

Keeping intermediate results of function composition

Suppose I've got the following three functions:

 val f1: Int => Option[String] = ???
 val f2: String => Option[Int] = ???
 val f3: Int => Option[Int]    = ???

I can compose them as follows:

 val f: Int => Option[Int] = x =>
   for {
     x1 <- f1(x)
     x2 <- f2(x1)
     x3 <- f3(x2)
   } yield x3

Suppose now that I need to keep the intermediate results of execution f1 , f2 , f3 and pass them to the caller:

class Result(x: Int) {
  val r1 = f1(x)
  val r2 = r1 flatMap f2
  val r3 = r2 flatMap f3
  def apply = r3 
}

val f: Int => Result = x => new Result(x)

Does it make sense ? How would you improve/simplify this solution ?

Homogenous List

It's pretty simple for single type, suppose

val g1: Int => Option[Int] = x => if (x % 2 == 1) None else Some(x / 2)
val g2: Int => Option[Int] = x => Some(x * 3 + 1)
val g3: Int => Option[Int] = x => if (x >= 4) Some(x - 4) else None

You can define

def bind[T]: (Option[T], T => Option[T]) => Option[T] = _ flatMap _
def chain[T](x: T, fs: List[T => Option[T]]) = fs.scanLeft(Some(x): Option[T])(bind)

And now

chain(4, g1 :: g2 :: g3 :: Nil)

will be

List(Some(4), Some(2), Some(7), Some(3))

preserving all intermediate values.

Heterogenous List

But we can do if there are multiple types involved?

Fortunately there is shapeless library for special structures named Heterogenous List which could handle list-like multi-typed sequences of values.

So suppose we have

import scala.util.Try

val f1: Int => Option[String] = x => Some(x.toString)
val f2: String => Option[Int] = x => Try(x.toInt).toOption
val f3: Int => Option[Int] = x => if (x % 2 == 1) None else Some(x / 2)

Lets define heterogenous analogues to previous functions:

import shapeless._
import ops.hlist.LeftScanner._
import shapeless.ops.hlist._

object hBind extends Poly2 {
  implicit def bind[T, G] = at[T => Option[G], Option[T]]((f, o) => o flatMap f)
}
def hChain[Z, L <: HList](z: Z, fs: L)
                         (implicit lScan: LeftScanner[L, Option[Z], hBind.type]) =
  lScan(fs, Some(z))

And now

hChain(4, f1 :: f2 :: f3 :: HNil)

Evaluates to

Some(4) :: Some("4") :: Some(4) :: Some(2) :: HNil

Class converter

Now if you urged to save your result in some class like

case class Result(init: Option[Int], 
                  x1: Option[String], 
                  x2: Option[Int], 
                  x3: Option[Int])

You could easily use it's Generic representation

just ensure yourself that

Generic[Result].from(hChain(4, f1 :: f2 :: f3 :: HNil)) == 
  Result(Some(4),Some("4"),Some(4),Some(2))

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