I am using a slight abuse of the builder pattern to make a fluent imperative execution chain. What I am after is a way to make it a compile error to forget the execute method at the end. My goal is something like the following
WithServiceA {
doStuff()
} WithServiceB {
doStuff()
} withClient client
WithServiceA
and WithServiceB
can both return values, so if the return value is used it is obvious if the return type is wrong, but if they are used imperatively, the whole object just falls on the floor silently. I want to ensure that forgetting the withClient
call is a compile error no matter what context it is used in.
I want to be able to skip blocks if they are unneeded and put them in an arbitrary order so I am looking to replace the nested inner class pattern that I was using previously ala
def onServiceA[A](body: ServiceA => A) = new {
def onServiceB[B >: A](body: ServiceB => B) = {b => {
doStuff()
}
}
It looks like type-safe builder pattern. See this answer.
In your case:
trait TTrue
trait TFalse
class Myclass[TA, TB, TC] private(){
def withServiceA(x: => Unit)(implicit e: TA =:= TFalse) = {x; new Myclass[TTrue, TB, TC]}
def withServiceB(x: => Unit)(implicit e: TB =:= TFalse) = {x; new Myclass[TA, TTrue, TC]}
def withServiceC(x: => Unit)(implicit e: TC =:= TFalse) = {x; new Myclass[TA, TB, TTrue]}
def withClient(x: => Unit)(implicit e1: TA =:= TTrue, e2: TB =:= TTrue) = x
}
object Myclass{
def apply() = new Myclass[TFalse, TFalse, TFalse]
}
Usage:
Myclass()
.withClient(println("withClient"))
//<console>:22: error: Cannot prove that TFalse =:= TTrue.
// .withClient(println("withClient"))
// ^
Myclass()
.withServiceB(println("with B"))
.withServiceA(println("with A"))
.withClient(println("withClient"))
//with B
//with A
//withClient
Myclass()
.withServiceA(println("with A"))
.withServiceC(println("with C"))
.withServiceB(println("with B"))
.withClient(println("withClient"))
//with A
//with C
//with B
//withClient
Myclass()
.withServiceC(println("with C"))
.withServiceB(println("with B"))
.withServiceA(println("with A"))
.withServiceC(println("with C2"))
.withClient(println("withClient"))
//<console>:25: error: Cannot prove that TTrue =:= TFalse.
// .withServiceC(println("with C2"))
// ^
You could provide custom error messages with custom replacements for =:=
class.
If you want to be sure that after every Myclass.apply
withClient
will be called, you could call it manually like this:
sealed class Context private()
object Context {
def withContext(f: Context => Myclass[TTrue, TTrue, _])(withClient: => Unit) =
f(new Context).withClient(withClient)
}
object Myclass{
def apply(c: Context) = new Myclass[TFalse, TFalse, TFalse]
}
Usage:
Context
.withContext(
Myclass(_)
.withServiceA(println("with A"))
.withServiceC(println("with C"))
.withServiceB(println("with B"))
)(println("withClient"))
On ideone.
One can't create Myclass
outside of withContext
method and withClient
will be called at least once.