Type safe method chaining that doesn't allow r

2019-01-19 02:39发布

问题:

I want to implement method chaining like in those questions:

Best practice to implement Scala trait which supports method chaining ;

Scala DSL: method chaining with parameterless methods

However, I want that once a "property" has been used, it cannot be used anymore. For example lets assume that I have a class "Myclass" for which I want to allow the use of definition "foo" and definition "bar" at most once and I don't care about the final return type. Thus:

val c = new Myclass
c foo //ok !
c foo bar // ok!
c foo foo // refuse to compile
c foo bar foo //refuse to compile

I'm struggling with this problem for a while and my vision starts becoming fuzzy! I tried to use implicit classes, however, whether they need to parse objects that has not used the associated property, and I can't find how, whether they need to "consume" the property by removing it from the object available property, and, again, I can't find how.

I'm currently searching in the reflection API, but it is still a little obscure for me.

Help would be appreciated! =)

回答1:

See Phantom Types In Haskell and Scala by James Iry.

You could also use type-safe builder pattern:

trait TTrue
trait TFalse

@annotation.implicitNotFound(msg = "Cannot call same method twice.")
sealed abstract class =^=[From, To]
object =^= {
  private val singleton_=^= = new =^=[Any, Any]{}
  implicit def tpEquals[A]: A =^= A = singleton_=^=.asInstanceOf[A =^= A]
}

class Myclass[TFoo, TBar, TBuz] private(){
  def foo(implicit e: TFoo =^= TFalse) = new Myclass[TTrue, TBar, TBuz]
  def bar(implicit e: TBar =^= TFalse) = new Myclass[TFoo, TTrue, TBuz]
  def buz(implicit e: TBuz =^= TFalse) = new Myclass[TFoo, TBar, TTrue]
}

object Myclass{
  def apply() = new Myclass[TFalse, TFalse, TFalse]
}

to be used like this

scala> Myclass().foo.bar.buz
res0: Myclass[TTrue,TTrue,TTrue] = Myclass@12ac706a

scala> Myclass().bar.buz.foo
res1: Myclass[TTrue,TTrue,TTrue] = Myclass@1e69dff6

scala> Myclass().foo.buz.foo
<console>:12: error: Cannot call same method twice.
              Myclass().foo.buz.foo
                                ^


回答2:

here you can ifnd an improved solution :

http://jim-mcbeath.blogspot.fr/2009/09/type-safe-builder-in-scala-part-3.html

(which actually relies on the phantom types)