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]()
}
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.
Upvotes: 3
Views: 252
Reputation: 67330
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"
Upvotes: 5
Reputation: 167911
You can't customize error messages, but you can customize your trait names. I'd call them Built
and Unbuilt
or something like that. Then you can warn users of the library or whatever that you'll get a hairy-looking error message, but they really only need to spot something that looks like Cannot prove that Built =:= Unbuilt
.
Upvotes: 2