假设我有一个班级,我想让它的方法可链接,我可以做这样的事情:
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"))
}
答案 0 :(得分: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")
这样的东西。
更新:另一种解决方案,基于从Unit
到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);
}
答案 1 :(得分:2)
为一元函数实现makeChainable
很容易,但如果你想支持更高的arity,它就会变得毛茸茸。除非你想为每个arity写一个单独的makeChainable
,否则我可以看到做方法二的唯一方法是对方法进行元组,将其传递给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"))
答案 2 :(得分:1)
我知道这可能不是你想要的,但你的描述让我想起了很多doto
construct in Clojure。
我找到了几个讨论将doto
移植到Scala的不同方法的线程:
something like Clojure's "doto"?
Re: something like Clojure's "doto"?(我认为这实际上是对第一个线程的回复,不知何故最终成为一个单独的线程)
通过这些线程,看起来最简单的方法就是使用短名称生成val
并将其用作重复语句的接收者。
或者创建隐式值类(在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"));
其他答案更多的是你正在寻找的,但我只是想指出另一种语言用来解决非可链接方法调用的替代方法。
答案 3 :(得分: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
。