Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type-safe Builder: How to set compiler error message

Tags:

scala

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

Builder().foo().foo() does not work as required, however I would like to set the error message to something user-readable. At the moment the message is

Multiple markers at this line - not enough arguments for method foo: (implicit ev: =:=[W.TTrue,W.TFalse])W.Builder[W.TTrue]. Unspecified value parameter ev. - Cannot prove that W.TTrue =:= W.TFalse. - Cannot prove that W.TTrue =:= W.TFalse.

like image 358
Manuel Schmidt Avatar asked Apr 19 '26 11:04

Manuel Schmidt


1 Answers

Using the type parameters here is a bit of an overkill. Better just return a less capable type from the foo method:

object Builder {
  trait CanFoo { def foo() : Builder }
  def apply(): Builder with CanFoo = new Builder with CanFoo {
    def foo() = new Builder {}
  }
}
trait Builder

Builder().foo().foo() // value foo is not a member of Builder

There is an annotation implicitNotFound which can be used to customise the error messages, but it needs to be defined with the type that is sought (=:=) not with the use site (foo), so that is a pretty useless construction...

...unless you create your own replacement for =:=:

import annotation.implicitNotFound

object Called {
  implicit def same[A]: Called[A, A] = instance.asInstanceOf[Called[A, A]]
  private object instance extends Called[Any,Any]
}
@implicitNotFound(msg = "Cannot call this method twice") sealed trait Called[A, B]

class Builder[Foo <: TBoolean] private() {
  def foo()(implicit ev: Called[Foo, TFalse]): Builder[TTrue] = {
    new Builder[TTrue]
  }
}
object Builder {
  def apply() = new Builder[TFalse]()
}

Builder().foo().foo()  // -> "error: Cannot call this method twice"
like image 94
0__ Avatar answered Apr 23 '26 07:04

0__