ABika
ABika

Reputation: 677

Method call with option value or default parameter in Scala

I'm rather new to Scala and stumbled upon a small little issue that keeps bothering me. Let's say there is some method with default parameter

def foo(v: Any = "default"): String = s"called with parameter '$v'"

and an Option val opt: Option[String]. How to call this method with either the option value (if defined) or the default parameter? I mean despite the obvious solution

val result = if (opt.isDefined)
               from.here.to.foo(opt.get)
             else
               from.here.to.foo()

and having to type the method with (possibly long) object chain twice? Not to mention having more than one optional/default parameter...


All I could come up with is the unhelpful helper

def definedOrDefault[A, B](opt: Option[A], f0: => B, f1: A => B): B =
  if (opt.isDefined) f1(opt.get) else f0

but when not being able to mention default parameters in higher order functions... that's it. Reminds me of the bad old days with Java where method overloading creates the same problem.

Upvotes: 10

Views: 19364

Answers (3)

Alexey Romanov
Alexey Romanov

Reputation: 170723

Slight extension to @Łukasz's answer which is too large to fit in a comment:

You can avoid having to wrap present parameters in Some without the dangerous any2option by creating a special-purpose type:

sealed trait OptParam[+A] { def toOption: Option[A] }
case class Param[+A](value: A) extends OptParam[A] { def toOption = Some(value) }
case object NoParam extends OptParam[Nothing] { def toOption = None }

object OptParam {
  implicit def any2optParam[A](x: A): OptParam[A] = Param(x)
}

def foo(v1: OptParam[String] = NoParam, v2: OptString[Int] = NoParam) = {
  val param1 = v1.toOption.getOrElse("default")
  val param2 = v2.toOption.getOrElse(42)
  s"'$param1', '$param2'"
}

foo("a") // "'a', '42'"

What makes this safer is that OptParam should only ever appear as a method parameter type, so the conversion won't get triggered where you don't expect it.

Upvotes: 2

Łukasz
Łukasz

Reputation: 8663

It seems like you are using two concepts of value being optional in one place, i.e. optional parameter and Option. They don't like play well together, maybe it is better to use just one.

If you always just pass value of Option to a method or pass nothing to get default value, maybe consider changing the function to accept the Option.

def foo(v: Option[String]) = {
  val param = v getOrElse "default"
  s"called with parameter '$param'"
}

If you still want to have default parameter you can change signature to

def foo(v: Option[String] = None)

This approach however will require you to wrap your parameter into Some when you do a regular call e.g.

foo(Some("regular param"))

but it works well when you use Options. You can easly add more optional parameters as well.

Here is an example

def foo(v1: Option[String] = None, v2: Option[Int] = None) = {
  val param1 = v1 getOrElse "default"
  val param2 = v2 getOrElse 42
  s"'$param1', '$param2'"
}

foo() // "default", 42

foo(v2 = Some(12)) // "default", 12

foo(Some("asd"), Some(11)) // "asd", 11

val optionFromSomewhere = Some("a")
val anotherOptionFromSomewhere = Option.empty[Int]
foo(optionFromSomewhere, anotherOptionFromSomewhere) // "a", 42

You could also introduce implicit conversion from Any to Option, that would let you to omit Some, but I don't think it is such a good idea

 implicit def any2option[A](e: A): Option[A] = Some(e)

Upvotes: 11

Martin Seeler
Martin Seeler

Reputation: 6982

You can use the map function defined on Option to transform the value wrapped inside, if it is defined. This looks like this:

opt.map(foo)
// same as
opt.map(x => foo(x))

This will return either an Option[String] if some value was inside, or None if it was None before.

Here is the full example from the REPL:

scala> def foo(v: Any = "default"): String = s"called with parameter '$v'"
foo: (v: Any)String

scala> Some("Hello")
res0: Some[String] = Some(Hello)

scala> res0.map(foo)
res1: Option[String] = Some(called with parameter 'Hello')

scala> val x: Option[String] = None
x: Option[String] = None

scala> x.map(foo)
res2: Option[String] = None

EDIT:

To call it with a default value, you should match your Option before your call, since the method expects a non Option parameter.

val optOrDefault = opt getOrElse "default"
foo(optOrDefault)

Upvotes: 0

Related Questions