9

2つのプロパティを定義する抽象クラスを考えてみましょう

abstract class A {
  def a: Int
  def b: Int
  // real A has additional members
}

これは、次のようなさまざまなケースクラスの基本クラスです。

case class Foo(a: Int, b: Int) extends A
case class Bar(a: Int, b: Int) extends A
// and many more

目標:最終的に、前述のケースクラスのインスタンスを2つの方法で作成できるようにしたいと思います。

val b1 = Bar(1, 2)
val b2 = Bar(1) has 2
assert(b1 == b2) // must hold

アプローチ:したがって、 shasを部分的に構築できるようにするヘルパークラスを定義することは合理的と思われます。A

case class PartialA(f: Int => A) {
  def has(b: Int) = f(b)
}

問題:現在の機械では、のような呼び出しは許可されていませんBar(1)。これは、実際には、の呼び出しBar.apply(1)、つまりapply、コンパイラによって生成されたオブジェクトによって定義されたメソッドの呼び出しであるためBarです。

Barコンパイラにオブジェクトをとして生成させることができれば素晴らしいと思いますobject Bar extends PartialAConstructor

abstract class PartialAConstructor{
  def apply(a: Int, b: Int): A // abstract, created when the compiler creates
                               // object Bar
  def apply(a: Int) = PartialA((b: Int) => apply(a, b))
}

ただし、ケースクラスのコンパニオンオブジェクトの生成に影響を与えることはできないようです。


必要なプロパティ:

  • ケースクラス:などは、構造的同等性などのコンパイラで生成された機能と、自動生成されたエクストラクタを使用したいので、ケースクラスのままにする必要がFooあります。Barcopy

  • 「完全な」構造的同等性:ケースクラスを次のように定義する

    case class Bar(a: Int)(val b: Int)
    

    コンパイラによって生成されたメソッドは引数の最初のリストのみを考慮するため、オプションではありません。equalsしたがって、次のことが誤って成立します。

    assert(Foo(1)(0) == Foo(1)(10))
    
  • コードの繰り返しをできるだけ少なくする:たとえば、もちろん、

    def Bar(a: Int) = PartialA((b: Int) => Bar(a, b))
    

    Aしかし、それは、Foo拡張するすべてのケースクラスに対して実行する必要がありますBar

4

3 に答える 3

3

Foo.applyカレー(そして、他の方法と同様に、自動的に関数にプロモートされるという事実)と、構文を強化するための少しのヘルパーに大きく依存することができます。

object partially {
  def apply[A1,A2,R]( f: (A1, A2) => R ) = f.curried
  def apply[A1,A2,R]( f: (A1, A2) => R, a1: A1 ) = f.curried( a1 )

  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R ) = f.curried
  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R, a1: A1 ) = f.curried( a1 )
  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R, a1: A1, a2: A2 ) = f.curried( a1 )( a2 )


  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R ) = f.curried
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1 ) = f.curried( a1 )
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1, a2: A2 ) = f.curried( a1 )( a2 )
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1, a2: A2, a3: A3 ) = f.curried( a1 )( a2 )( a3 )
  // ... and so on, potentially up to 22 args
}

次に、次のことができます。

scala> val x = partially(Foo)(1)
x: Int => Foo = <function1>
scala> x(2)
res37: Foo = Foo(1,2)

has(関数を直接適用するのではなく)本当にメソッドを使用したい場合は、その上に暗黙のクラスをスローします。

implicit class Func1Ops[-A,+R]( val f: A => R ) extends AnyVal { 
  def has( arg: A ): R = f( arg ) 
}

そして今、あなたはできる:

scala> val x = partially(Foo)(1)
x: Int => Foo = <function1>

scala> x has 2
res38: Foo = Foo(1,2)
于 2013-02-05T14:01:56.723 に答える
2

どうしたの

val x = Foo(1, _: Int)

コンパニオンにメソッドを追加することもできます。このapplyメソッドは、1つの引数のみを取り、部分適用を実行します。

それ以外に、まだリリースされていないタイプマクロでそれを行う方法があるかもしれませんが、マクロパラダイスでそれらを試すことができます。

編集

ケースクラスのコンパニオンに何かを追加するには、通常どおりに実行します。

case class Foo(x: Int, y: Int)

object Foo {
  def apply(x: Int): (Int => Foo) = Foo(x, _: Int)
}

scala> Foo(1,2)
res3: Foo = Foo(1,2)

scala> Foo(1)
res4: Int => Foo = <function1>

適用では、PartialAまたは好きなものを返すこともできます。

于 2013-02-05T13:17:57.200 に答える
1

あなたが本当に「持っている」DSLが欲しい、そして多分それを拡張可能にしたいと仮定すると、以下も機能します:

abstract class A {
  def a: Int
  def b: Int
}    

trait PartialHas[T] { 
    self: { def apply(a: Int, b: Int): T } => 
    trait HasWord { def has(b: Int): T }
    def apply(a: Int): HasWord = new HasWord { def has(b: Int): T = apply(a, b) } 
}    

case class Bar(a: Int, b: Int) extends A
object Bar extends PartialHas[Bar]

クラスマクロを使用して、明示的なコンパニオン定義を完全に廃止する方法があるかもしれません。

于 2014-03-20T18:38:57.250 に答える