Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I programmatically make methods chainable?

Tags:

scala

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 = {
    println("doing stuff")
    this
  }

  def methodTwo(arg1: Any, arg2: Any): MyClass = {
    println("doing other stuff")
    this
  }
}

While that would achieve the functionality I'm looking for, it's not very elegant in my opinion. Is there a better way of doing this?

Assuming it's possible, I'd like to be able to do something like the following, but I'm not sure how to approach the makeChainable function.

class MyClass {

  val methodOne: Any => MyClass = 
    makeChainable((arg1: Any) => println("doing stuff"))

  val methodTwo: (Any, Any) => MyClass = 
    makeChainable((arg1: Any, arg2: Any) => println("doing other stuff"))

}
like image 639
jharding Avatar asked Jul 17 '13 06:07

jharding


3 Answers

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);
}
like image 156
Petr Avatar answered Sep 28 '22 01:09

Petr


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"))
like image 25
Chris Martin Avatar answered Sep 27 '22 23:09

Chris Martin


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.

like image 26
DaoWen Avatar answered Sep 28 '22 01:09

DaoWen