1

Suppose I have the following functions

def foo(x: Int): Option[String] = ...
def bar(s: String): Option[String] = ...

and

def f(s: String): String = ...
def g(s: String): String = ...

I would like to compose all these functions as follows:

def qux(x: Int): String = {
   val s1 = foo(x).fold("defaultFoo") { str => f(str) }
   bar(s1).fold("defaultBar") { str => g(str) }
}

In my opinion function qux does not look particularly elegant and I wonder how to simplify it. How would you suggest write qux ?

Artemis
  • 2,553
  • 7
  • 21
  • 36
Michael
  • 41,026
  • 70
  • 193
  • 341

2 Answers2

2

With simple usage of map:

def quz(x: Int) = 
  bar(foo(x).fold("defaultFoo")(f))
    .map(g).getOrElse("defaultBar")
acjay
  • 34,571
  • 6
  • 57
  • 100
4lex1v
  • 21,367
  • 6
  • 52
  • 86
1

To codify what other's have said, the first major improvement would be to let eta-expansion work for you instead of manually making closures:

def qux(x: Int): String = {
  val s1 = foo(x).fold("defaultFoo")(f)
  bar(s1).fold("defaultBar")(g)
}

You could also use a for...yield comprehension to defer your default case until the end:

def qux(x: Int): String = {
   val result = for { s1 <- foo(x); s2 <- bar(f(s1)) } yield { g(s2) } 
   result.getOrElse("default result")
}

The issue here is that you lose the error handling you have in the middle of your control flow, because the simpler combinators pass through None cases. If you need to preserve that same flow, you might try this:

def qux(x: Int): String = {
   val s1 = foo(x).map(f).getOrElse("defaultFoo") 
   bar(s1).map(g).getOrElse("defaultBar")
}

It's not hugely simpler than your original code, but it's a little terser, and pushes error handling to the end of each line. Or just leave out the intermediate variable, and make it a one-liner:

def qux(x: Int): String = bar(foo(x).map(f).getOrElse("defaultFoo")).map(g).getOrElse("defaultBar")
acjay
  • 34,571
  • 6
  • 57
  • 100