In this case Rafael's article "Type-safe Builder Pattern in Scala" will probably shake your world.
To put it in simple terms, let's say we have a constructor signature for type Foo:
case class Foo(val a:A, val b:Option[B])And the corresponding builder which emulates a Java-ish implementation:
class FooBuilder {
var a:Option[A] = None
var b:Option[B] = None
def withA(a:A) = {this.a = Some(a); this}
def withB(b:B) = {this.b = Some(b); this}
def build() = new Foo(a.get, b)
}
This is self-explanatory. A careful reader might wonder what happens when the mandatory variable 'a' remains unbound?
scala> new FooBuilder() withB(B) build java.util.NoSuchElementException: None.getSimple answer, for non-optional parameters (here: a:Option[A]) the Option[A].get method throws an exception at run-time if a is unassigned (or more precisely: is assigned to an instance of None).
Okay, but where's the magic, you say? Good, imagine we could statically ensure that non-optional parameters are bound at compile-time. Got it? Then let's take a shortcut - we transform the builder code into a functional-inspired form and use phantom types as a constraint for instantiation:
object BuilderPattern {
case class A
case class B
case class Foo(val a:A, val b:Option[B])
abstract class TRUE
abstract class FALSE
class FooBuilder[HasA, HasB](val a:Option[A], val b:Option[B]) {
def withA(a:A) = new FooBuilder[TRUE, HasB](Some(a),b)
def withB(b:B) = new FooBuilder[HasA, TRUE](a,Some(b))
}
implicit def enableBuild(builder:FooBuilder[TRUE,FALSE]) = new {
def build() = new Foo(builder.a.get, builder.b) }
def builder = new FooBuilder[FALSE,FALSE](None, None)
}
Let's give it a try:
scala> import BuilderPattern._ scala> builder withA(A()) build scala> builder withB(B()) build :12: error: value build is not a member of BuilderPattern.FooBuilder[BuilderPattern.FALSE,BuilderPattern.TRUE]I hope your brain screams "SWEET!". Didn't get it? Then enjoy Rafael's lengthy version "Type-safe Builder Pattern in Scala".
No comments:
Post a Comment