6

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! =)

Community
  • 1
  • 1
Coo LHibou
  • 149
  • 5

2 Answers2

14

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
                                ^
Community
  • 1
  • 1
senia
  • 37,745
  • 4
  • 88
  • 129
  • +1 for Type Builders. But this implementation is tightly coupled to the number of methods, how about adding 10 new methods? And such an error in production would be strange – 4lex1v Jul 26 '13 at 14:55
  • @AlexIv: For 10 methods you'll have to add 10 type parameters. Error message fixed. It's a compilation error, not runtime production error. – senia Jul 26 '13 at 15:08
  • 1
    Thank you for the answer. I understood the main idea : you have a TTrue/TFalse flag for each option that you switch when the option is activated. In order to detect the activation of an option at compile time you request the implicit instanciation of an object, object that can be implicitely instanciated if and only if the flag has the right value. Hence, if the option has been called, the flag is at true, the implicit instanciation is not possible and the compiler failed by throwing the test in annotation. I think it is a very elegant solution. – Coo LHibou Jul 30 '13 at 08:56
  • However my objective is to use the pattern decorator to provide an extensible optionable framework therefor I do not know by anticipation what would be the options, thus I can not use this solution. I think something might be done for this using a mutable map... I'm investigating it as well as the macro type solution that may allow to directly generate the appropriate type – Coo LHibou Jul 30 '13 at 08:59
0

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)

Coo LHibou
  • 149
  • 5