Type-safe Builder: How to combine phantom types

2020-06-25 09:53发布

问题:

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]
    }
  }

回答1:

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
}


标签: scala