Let\'s say I have a class and I want to make its methods chainable, I could do something like this:
class MyClass {
def methodOne(arg1: Any): MyClass = {
The first option is the most efficient one, the other one introduces overhead by wrapping code into function object. But it's certainly possible to create such a wrapper. Let's define
trait Chainable {
final def mkChain(f: () => Any): () => this.type =
() => { f(); this; }
final def mkChain[A](f: (A) => Any): (A) => this.type =
(x: A) => { f(x); this; }
final def mkChain[A,B](f: (A,B) => Any): (A,B) => this.type =
(x: A, y: B) => { f(x, y); this; }
// etc. for other arities
}
Notice this.type
, it says the result of our functions is the type of the class they're defined in. So now when we mix it into our class
class MyClass extends Chainable {
val methodTwo =
mkChain((x: Any, y: String) => println("Doing something " + y));
}
the result of methodTwo
will be MyClass
.
Update: There is another option, to use implicit conversions:
trait ToChain {
implicit class AsThis(val _underlying: Any) {
def chain: ToChain.this.type = ToChain.this
}
}
class MyClass2 extends ToChain {
def methodOne(arg1: Any): Unit =
println("Doing something")
def methodTwo(arg1: String): Unit =
println("Doing something else " + arg1)
methodOne(3).chain.methodTwo("x");
}
Calling chain
converts anything to this.type
. However it only works inside the class, you can't call something like new MyClass2.methodOne(3).chain.methodTwo("x")
outside.
Update: Yet another solution, based on implicit conversion from Unit
to this
:
import scala.language.implicitConversions
class Chain[A](val x: A) {
implicit def unitToThis(unit: Unit): A = x;
}
implicit def unchain[A](c: Chain[A]): A = c.x;
// Usage:
val r: MyClass = new Chain(new MyClass) {
x.methodOne(1).methodTwo(2,3);
}
Leaving aside the question of how wise this is in the first place, it's pretty easy to implement in a type-safe and boilerplate-free way with Shapeless:
import shapeless._
trait ChainableUtils {
def makeChainable[F, Args <: HList](f: F)(implicit
in: FnHListerAux[F, Args => Unit],
out: FnUnHLister[Args => this.type]
) = out((a: Args) => { in(f)(a); this })
}
And then:
scala> class MyClass extends ChainableUtils {
| def func1 = makeChainable((i: Int) => println("Doing stuff."))
| def func2 = makeChainable((a: Any, b: Any) =>
| println("Doing other stuff."))
| }
defined class MyClass
scala> val myInstance = new MyClass
myInstance: MyClass = MyClass@6c86b570
scala> myInstance.func1(1).func2('a, "a").func1(42)
Doing stuff.
Doing other stuff.
Doing stuff.
res0: myInstance.type = MyClass@6c86b570
This will work for any FunctionN
.
I know this isn't probably exactly what you're looking for, but your description reminds me a lot of the doto construct in Clojure.
I found a couple of threads discussing the different ways of porting doto
to Scala:
something like Clojure's "doto"?
Re: something like Clojure's "doto"? (I think this was actually a reply to the first thread that somehow ended up as a separate thread)
Looking through those threads, it looks like the easiest way is just to make a val
with a short name and use that as the receiver of repeated statements.
Or create an implicit value class (available in Scala 2.10):
implicit class Doto[A](val value: A) extends AnyVal {
def doto(statements: (A => Any)*): A = {
statements.foreach((f: A => Any) => f(value))
value
}
}
new MyClass2().doto(_.methodOne(3), _.methodTwo("x"));
The other answers are much more what you're looking for, but I just wanted to point out an alternate approach another language took for working around non-chainable method calls.
It's easy to implement makeChainable
for unary function, but it gets hairy if you want to support higher arity. The only way I can see to do method two, unless you want to write a separate makeChainable
for every arity, is to tuple the method, pass it through makeChainable
, and then untuple it.
class MyClass {
def methodOne: Any => MyClass = makeChainable {
(arg1: Any) => println("doing stuff")
}
def methodTwo: (Any, Any) => MyClass = Function untupled makeChainable {(
(arg1: Any, arg2: Any) => println("doing other stuff")
).tupled}
def makeChainable[A](f: (A) => Unit): (A => MyClass) = { a: A => f(a); this }
}
new MyClass().methodOne("a").methodTwo("b", "c")
But - and please forgive me for opining - invocation chaining is generally a shortcut you take in other languages that are less expressive than Scala. Unless you're doing this to make an API for Java users, I think this is a really bad idea.
Here's one alternative, which I still would never do, to accomplish roughly the style you're going for in a way that's less invasive:
class MyClass {
def methodOne(a: Any) { println("doing stuff") }
def methodTwo(a: Any, b: Any) { println("doing other stuff") }
def apply(fs: (MyClass => Unit)*) { fs.foreach(f => f(this)) }
}
new MyClass()(_.methodOne("a"), _.methodTwo("b", "c"))
Edit:
A more elegant way would be to define a "kestrel combinator". I do think this approach is legit :)
class MyClass {
def methodOne(a: Any) { println("doing stuff") }
def methodTwo(a: Any, b: Any) { println("doing other stuff") }
}
implicit class Kestrel[A](x: A) {
def ~(f: A => Unit): A = { f(x); x }
}
new MyClass() ~ (_.methodOne("a")) ~ (_.methodTwo("b", "c"))