spiffman spiffman - 2 months ago 8
Scala Question

Proper way to guard function operations using Option[] arguments

I have code where a class can provide modified copies of itself, like so:

case class A(i: Int, s: String) {
def foo(ii: Int): A = copy(i = ii)
def bar(ss: String): A = copy(s = ss)
}


I want to create a function that takes some optional arguments and creates these modified copies using these arguments if they are defined:

def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
if (oi.isDefined && os.isDefined)
a.foo(oi.get).bar(os.get)
else if (oi.isDefined && !os.isDefined)
a.foo(oi.get)
else if (!oi.isDefined && os.isDefined)
a.bar(os.get)
else
a
}


This is clearly not sustainable, as I add new optional arguments, I have to create cases for every combination of arguments...

I also cannot do:

a.foo(oi.getOrElse(a.i)).bar(os.getOrElse(a.s))


Because in my actual code, if
oi
or
os
is not provided, I should NOT run their associated
foo
and
bar
functions. In other words, I have no default arguments for
oi
and
os
, rather their existence defines whether I should run certain functions at all.

Current solution, extend the class:

implicit class A_extended(a: A) {
def fooOption(oi: Option[Int]): A = if (oi.isDefined) a.foo(oi.get) else a
def barOption(os: Option[String]): A = if (os.isDefined) a.bar(os.get) else a
}

def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
a.fooOption(oi).barOption(os)
}


But this problem comes up often and it's a bit tedious to do this constantly, is there something like:

// oi: Option[Int], foo: Int => A
oi.ifDefinedThen(a.foo(_), a) // returns a.foo(oi.get) if oi is not None, else just a


Or should I just extend
Option
to provide this functionality?

Answer

Use fold on option final def fold[B](ifEmpty: => B)(f: A => B): B

def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
       val oia = oi.fold(a)(a.foo)
       os.fold(oia)(oia.bar)
}

Scala REPL

scala> def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
   val oia = oi.fold(a)(a.foo)
   os.fold(oia)(oia.bar)
  }
defined function subA

scala> subA(A(1, "bow"), Some(2), Some("cow"))
res10: A = A(2, "cow")

or

Use pattern matching to deal with options elegantly. Create a tuple of options and then use pattern matching to extract the inner values

val a = Some(1)

val b = Some("some string")

(a, b) match {

 case (Some(x), Some(y)) =>

 case (Some(x), _) =>

 case (_, Some(y)) =>

 case (_, _) =>

}