Max Amanshauser
Max Amanshauser

Reputation: 73

Override default argument only when condition is fulfilled

Given

case class Foo (
  x: Int = 1,
  y: String,
)

What is the best way to instantiate said class, overwriting default params only if a local condition is fulfilled (e.g. the local variable corresponding to the constructor parameter is not None)

object Test {
  /* Let's pretend I cannot know the state of x,y
   * because they come from the network, a file... */
  val x: Option[Int] = getX()
  val y: Option[String] = getY()

  Foo(
    x=???,   // how to get x=if(x.isDefined) x else "use default of Foo" here
    y=y.get,
  )
}

The straightforward solution of checking the condition and instantiating the case class differently does not scale (seven arguments with default values -> 128 different instantiations)

Solution 1) One solution I know is:

object Foo {
  val Defaults = Foo(y="")
}

object Test {
  val x: Option[Int] = getX()
  val y: Option[String] = getY()

  Foo(
    x=x.getOrElse(Foo.Defaults.x)
    y=y.get
  )
}

This works ok-ish. When y is None I get the NoSuchElementException, but that's OK because it is a mandatory constructor argument. However, this is clearly a hack and has the distinct drawback that it is possible to write:

  Foo(
    x=x.getOrElse(Foo.Defaults.x)
    y=y.getOrElse(Foo.Defaults.y)
  )

When y is None you get a non-sensical default value for y.

Solution 2) Another solution is something like:

sealed trait Field
case object X extends Field

object Foo {
  private val template = Foo(y="")
  val Defaults = {
    case X => template.x
  }
}

object Test {
  val x: Option[Int] = getX()
  val y: Option[String] = getY()

  Foo(
    x=x.getOrElse(Foo.Defaults(X))
    y=y.get
  )
}

This is a bit better type safety-wise, but now I have to create a type for each default parameter.

What would a correct and concise solution look like?

Upvotes: 4

Views: 976

Answers (1)

Dave Swartz
Dave Swartz

Reputation: 910

Not clear to me how you can do better than the following:

object Foo { def apply(optX: Option[Int], optY: Option[String]): Foo = Foo(optX.getOrElse(1), optY.get) }
case class Foo private(x: Int, y: String)

Foo(Some(5), None) // fails with NoSuchElementException
Foo(Some(5), Some("Hi")) // succeeds in creating Foo(5, "Hi")
Foo(None, Some("Hi")) // succeeds in creating Foo(1, "Hi"), note the default value of x

Whether a parameter is required or optional with a default is encoded via the invocation of get for the former and getOrElse for the latter.

Of course, you could wrap the Option's get method to provide a more meaningful message when required parameters are omitted.

I realize that is not far from your solution 1 and may not meet your needs.

Upvotes: 1

Related Questions