7

假设我有一个类,我想让它的方法可链接,我可以这样做:

class MyClass {

  def methodOne(arg1: Any): MyClass = {
    println("doing stuff")
    this
  }

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

虽然这将实现我正在寻找的功能,但在我看来它并不是很优雅。有没有更好的方法来做到这一点?

假设有可能,我希望能够执行以下操作,但我不确定如何使用该makeChainable功能。

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

}
4

4 回答 4

4

第一种选择是最有效的一种,另一种通过将代码包装到函数对象中来引入开销。但是当然可以创建这样的包装器。让我们定义

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
}

注意this.type,它说我们函数的结果是它们定义的类的类型。所以现在当我们将它混合到我们的类中时

class MyClass extends Chainable {
  val methodTwo =
    mkChain((x: Any, y: String) => println("Doing something " + y));
}

的结果methodTwo将是MyClass


更新:还有另一种选择,使用隐式转换:

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

调用chain将任何内容转换为this.type. 但是它只在课堂内有效,你不能new MyClass2.methodOne(3).chain.methodTwo("x")在外面打电话。


更新:另一个解决方案,基于从Unitto的隐式转换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);
}
于 2013-07-17T06:50:07.820 回答
2

一元函数很容易实现makeChainable,但如果你想支持更高的arity,它会变得很麻烦。我能看到的方法二的唯一方法,除非你想makeChainable为每个 arity 编写一个单独的方法,否则就是对方法进行元组化,将其传递给makeChainable,然后将其解组。

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

但是 - 请原谅我的意见 - 调用链通常是您在其他语言中采用的捷径,这些语言的表达力不如 Scala。除非您这样做是为了为 Java 用户创建 API,否则我认为这是一个非常糟糕的主意。

这是一种我仍然不会做的替代方法,以一种侵入性较小的方式大致完成您想要的风格:

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

编辑:

一种更优雅的方法是定义“红隼组合器”。我确实认为这种方法是合法的:)

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"))
于 2013-07-17T06:47:34.707 回答
1

我知道这可能不是您正在寻找的内容,但您的描述让我想起了dotoClojure 中的很多构造

我发现了几个讨论移植doto到 Scala 的不同方法的线程:

类似于 Clojure 的“doto”?

回复:像 Clojure 的“doto”之类的东西?(我认为这实际上是对第一个线程的回复,不知何故最终成为一个单独的线程)

浏览这些线程,看起来最简单的方法就是val用一个短名称制作 a 并将其用作重复语句的接收器。

或者创建一个隐式值类(在 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"));

其他答案更多的是您正在寻找的,但我只是想指出另一种语言用于解决不可链接方法调用的替代方法。

于 2013-07-17T06:59:41.260 回答
0

撇开这首先是多么明智的问题不谈,使用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 })
}

接着:

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

这适用于任何FunctionN.

于 2013-07-17T17:06:31.720 回答