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