How can I programmatically make methods chains?

Say I have a class and I want to make its methods integer, 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 this would provide the functionality I am looking for, it is not very elegant in my opinion. Is there a better way to do this?

Assuming this is possible, I would like 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")) } 
+6
source share
4 answers

The first option is the most effective, the other is overhead by transferring the code to the function object. But it is certainly possible to create such a shell. Let 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 } 

Pay attention to this.type , it says that the result of our functions is the type of the class in which they are defined. So now when we mix it with 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 use case for 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"); } 

A chain call converts anything to this.type . However, it only works inside the class, you cannot call something like new MyClass2.methodOne(3).chain.methodTwo("x") outside.


Update: 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 = cx; // Usage: val r: MyClass = new Chain(new MyClass) { x.methodOne(1).methodTwo(2,3); } 
+3
source

It's easy to implement makeChainable for a unary function, but it gets hairy if you want to maintain a higher arity. The only way I can make method two, if you do not want to write a separate makeChainable for each of them, is to encode the method, pass it through makeChainable , and then disable 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 what you use - the call chain is usually a shortcut that you use in other languages ​​that are less expressive than Scala. If you are not doing this to create an API for Java users, I think this is a really bad idea.

Here is one option that I would never have done in order to accomplish something like the style you are doing so that it is 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 "null combinator". I really think this approach is legal :)

 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")) 
+2
source

I know this is probably not exactly what you are looking for, but your description reminds me a lot of the doto construct in Clojure .

I found a couple of threads discussing various ways to port doto to Scala:

something like Clojure "doto"?

Re: Something like Clojure "doto"? (I think this was the answer to the first thread that somehow ended up with a separate thread)

Looking through these streams, it seems that the easiest way is to simply make val with a short name and use it as a receiver of duplicate 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 important than what you are looking for, but I just wanted to point out an alternative approach used by another language to work with non-chaining calls.

+1
source

Leaving aside the question of how wise it is in the first place, it is quite easy to implement it in a safe type and without templates using 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 .

0
source

Source: https://habr.com/ru/post/949626/


All Articles