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

最初のオプションは最も効率的なオプションですが、もう 1 つのオプションはコードを関数オブジェクトにラップすることによってオーバーヘッドを導入します。しかし、そのようなラッパーを作成することは確かに可能です。定義しましょう

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);
}
于 2013-07-17T06:50:07.820 に答える
1

これはおそらくあなたが探しているものではないことはわかっていますが、あなたの説明dotoは Clojure の多くの構成を思い出させます。

dotoScala へのさまざまな移植方法について議論しているスレッドをいくつか見つけました。

Clojureの「doto」のようなもの?

Re: 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 に答える