How can I programmatically make methods chainable?

前端 未结 4 1518
栀梦
栀梦 2021-02-09 05:30

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 = {
             


        
4条回答
  •  别跟我提以往
    2021-02-09 06:19

    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"))
    

提交回复
热议问题