Reputation: 2489
I am using phantom types in the type-safe builder pattern to ensure methods are called only once as in the following code sample
sealed trait TBoolean
sealed trait TTrue extends TBoolean
sealed trait TFalse extends TBoolean
class Builder[MethodCalled <: TBoolean] private() {
def foo()(implicit ev: MethodCalled =:= TFalse): Builder[TTrue] = {
new Builder[TTrue]
}
}
object Builder {
def apply() = new Builder[TFalse]()
}
I really appreciate this approach since one can use the .
-operator to chain method calls (unlike with other approaches)
However, this becomes unhandy if there are many methods to guard ending with something like
class Builder[MethodCalled1 <: TBoolean, MethodCalled2 <: TBoolean, ... ,MethodCalledN <: TBoolean]
Is there a way to create a "type struct"? Something like the following pseudo code:
type S {
type MethodCalled1 <: TBoolean
type MethodCalled2 <: TBoolean
...
type MethodCalledN <: TBoolean
}
class Builder[S] private() {
def foo()(implicit ev: S#MethodCalled1 =:= TFalse): Builder[S#MethodCalled1.TTrue] = {
new Builder[S#MethodCalled1.TTrue]
}
}
Upvotes: 2
Views: 548
Reputation: 4891
You were on the right track, you just needed to add a little type refinement:
trait BuilderMethods {
type FooCalled <: TBoolean
type BarCalled <: TBoolean
}
class Builder[M <: BuilderMethods] private() {
def foo()(implicit ev: M#FooCalled =:= TFalse): Builder[M {type FooCalled = TTrue}] = {
new Builder[M {type FooCalled = TTrue}]
}
def bar()(implicit ev: M#BarCalled =:= TFalse): Builder[M {type BarCalled = TTrue}] = {
new Builder[M {type BarCalled = TTrue}]
}
}
object Builder {
type UnusedBuilder = BuilderMethods {type FooCalled = TFalse; type BarCalled = TFalse;}
def apply(): Builder[Builder.UnusedBuilder] = new Builder[UnusedBuilder]()
}
object TestPhantomStruct extends App {
val newBuilder = Builder()
val builderFooCalled = newBuilder.foo()
val builderFooCalledTwice = builderFooCalled.foo() // will not compile
val builderFooCalledBarCalled = builderFooCalled.bar()
val builderFooCalledTwiceBarCalled = builderFooCalledBarCalled.foo() // will not compile
val builderBarCalled = newBuilder.bar()
val builderBarCalledTwice = builderBarCalled.bar() // will not compile
}
Upvotes: 5