Reputation: 73
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
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