Reputation: 47
I want to have a flexible generator for case class.
case class ABC(a: String, b: String, c: String)
I want to write in any order:
val genAbc = (new Gen).withB("b").withA("a").withC("c")
And be able to convert it to case class if all parameters are set:
genAbc.toAbc
If not all parameters are set, I want to get compile error:
(new Gen).withA("a").toAbc //<- error
Is it possible?
Upvotes: 1
Views: 3923
Reputation: 434
Maybe you are looking for something simple like a case class copy
method (you indeed cannot miss one of the params and you can specify them in random order):
case class ABC(a: String, b: String, c: String)
val abc = ABC("a", "b", "c")
println(abc)
// ABC(a,b,c)
val acc = abc.copy(b = "c")
println(acc)
// ABC(a,c,c)
Upvotes: 4
Reputation: 11607
There is a type-safe version of the builder pattern in Scala, see http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html
But, if you're willing to give up the requirement to be able to set the parameters in any order, there is a much simpler way:
scala> val abcBuilder = (Abc.apply _).curried
abcBuilder: String => (String => (String => Abc)) = <function1>
That creates a curried function using the Abc
class's smart constructor (Abc.apply
). A curried function lets you pass in each argument separately. In the interim you keep getting back more functions which take one argument and get you one step closer to an Abc
instance. So:
scala> abcBuilder("a")
res11: String => (String => Abc) = <function1>
scala> res11("b")
res12: String => Abc = <function1>
scala> res12("c")
res13: Abc = Abc(a,b,c)
This is all also type-safe (to the extent that you use distinct types for the parameters); so e.g. abcBuilder(1)
would give you a compile error.
Upvotes: 4
Reputation: 4751
You could do something like this:
class Gen {
def withA(a: String) = GenWithA(a)
def withB(b: String) = GenWithB(b)
def withC(c: String) = GenWithC(c)
}
case class GenWithA(a: String) {
def withB(b: String) = GenWithAB(a, b)
def withC(c: String) = GenWithAC(a, c)
}
case class GenWithB(b: String) {
def withA(a: String) = GenWithAB(a, b)
def withC(c: String) = GenWithBC(b, c)
}
case class GenWithC(c: String) {
def withA(a: String) = GenWithAC(a, c)
def withB(b: String) = GenWithBC(b, c)
}
case class GenWithAB(a: String, b: String) {
def withC(c: String) = GenWithABC(a,b,c)
}
case class GenWithAC(a: String, c: String) {
def withB(b: String) = GenWithABC(a,b,c)
}
case class GenWithBC(b: String, c: String) {
def withA(a: String) = GenWithABC(a,b,c)
}
case class GenWithABC(a: String, b: String, c: String) {
def toABC = ABC(a,b,c)
}
Two issues here:
First you get a combinatorial explosion once you start going greater than 3 parameters. You can generate it in SBT or with macros or shapeless magic possibly but all are a lot of work.
Second, this kind of code won't typecheck the way you want it to:
val g0 = new Gen
val g1 = if (condition) g0.withA("a") else g0.withB("b")
val g2 = if (condition) g1.withB("b") else g1.withA("a")
val g3 = g2.withC("c")
Like theoretically this always produces something valid, but the scala compiler is not smart enough to figure that out, so you can't use different parts of the builder in different conditional branches.
That doesn't leave you with much. If your goal is to simply write parameters in any order, the simpler thing is to just specifiy them as named parameters:
ABC(
c = "C First",
b = "Then B",
a = "Finally A"
)
Occasionally builders with a type for each stage like that are useful, say, in building query DSLs but for building a data case class, it is almost always clunky overkill
Upvotes: 1