2012-03-14 6 views
10

の線形化は、次のように私はケースクラスStepperを書きたいと仮定:ケースクラスと特性

case class Stepper(step: Int) {def apply(x: Int) = x + step} 

それは素敵なtoString実装が付属しています:

scala> Stepper(42).toString 
res0: String = Stepper(42) 

しかし、それは本当に機能ではありません:

scala> Some(2) map Stepper(2) 
<console>:10: error: type mismatch; 
found : Stepper 
required: Int => ? 
       Some(2) map Stepper(2) 

回避策は、Functionトレイト...

case class Stepper(step: Int) extends (Int => Int) {def apply(x: Int) = x + step} 

しかし、その後、私はもう自由な素敵なのtoStringの実装を持っていることはできませんが:

scala> Stepper(42).toString 
res2: java.lang.String = <function1> 

その後、質問は:私は、これら2つの長所を持つことができます?無料を実装したtoStringの実装は、Functionという優れた実装方法がありますか?言い換えれば、最後にcase classシンタックスシュガーが適用されるように線形化を適用する方法はありますか?

答えて

8

質問は実際に線形化とは関係ありません。 case-classes toStringは、Any.toStringがend-typeでオーバーライドされない場合にのみ、コンパイラによって自動的に生成されるメソッドです。

しかし、答えは線形化と関係して、部分的である - 私たちはされていない場合Function1により導入されたバージョンのコンパイラによって生成されたであろう方法でFunction1.toStringをオーバーライドする必要があります。そして、

trait ProperName extends Product { 
    override lazy val toString = scala.runtime.ScalaRunTime._toString(this) 
} 

// now just mix in ProperName and... magic! 
case class Stepper(step: Int) extends (Int => Int) with ProperName { 
    def apply(x:Int) = x+step 
} 

println(Some(2) map Stepper(2)) 
println(Stepper(2)) 

演出します

Some(4) 
Stepper(2) 

更新

ここでは、文書化されていないAPIメソッドに依存しないProperName形質のバージョンです:

trait ProperName extends Product { 
    override lazy val toString = { 
    val caseFields = { 
     val arity = productArity 
     def fields(from: Int): List[Any] = 
     if (from == arity) List() 
     else productElement(from) :: fields(from + 1) 
     fields(0) 
    } 
    caseFields.mkString(productPrefix + "(", ",", ")") 
    } 
} 

代替toString実装が元_toStringのソースコードに由来しています方法scala.runtime.ScalaRunTime._toString

この代替実装は、ケースクラスが常にProductの特性を拡張するという前提に基づいています。後者はScala 2.9.0の時点で当てはまりますが、Scalaコミュニティの一部のメンバーに知られており、それに依存しているという事実は、Scala Language Specの一部として正式に文書化されていません。

+0

はい、実際には線形化ではありませんが、他の適切な名前は見つかりませんでした。そして、それは私が期待していた種類のトリックでした、ありがとうございました。 – Nicolas

+0

@Nicolas私はあなたをとてもよく理解しています。何が起きているのかわからないときには、問題を正確に記述することが困難なことがよくあります。 –

+0

@Vlad:私はScala APIドキュメントには表示されない 'scala.runtime'からの何かの使用を避けてきました。私はこれが賢明な回避策であることに同意しますが、平凡な古いScala言語機能を使用する同様の優れたソリューションがあることを考えると、実際には価値があると思いますか? –

2

編集: toStringのオーバーライドはどうですか?

case class Stepper(step: Int) extends (Int => Int) { 
    def apply(x: Int) = x + step 
    override def toString = "Stepper(" + step + ")" 
} 
+0

はい、わかっています。私は実際に適用しない(DSLで想像してみてください)を追加したくありません。 – Nicolas

+0

ああ、OK。これはどう? –

+0

@TalPressmanあなたはFunction1を拡張するのを忘れました –

1

あなたは必要な場合のみStepperが関数のように扱わ持つように暗黙的な変換を使用することができます:あなたはStepper(42).toStringを呼び出すとき

case class Stepper(step: Int) { def apply(x: Int) = x + step } 

implicit def s2f(s: Stepper) = new Function[Int, Int] { 
    def apply(x: Int) = s.apply(x) 
} 

は今、あなたはケースクラスのtoStringを得るが、必要に応じてSome(2) map Stepper(2)も動作します。

(私は、メカニックを明確にするために上記よりも冗長になっていることに注意してください。implicit def s2f(s: Stepper) = s.apply _または他のいくつかのより簡潔な形式も書くことができます)。

+0

はい、これを暗黙的に構築するのは、toStringを書き換えるよりも痛いです。 – Nicolas

+0

@Nicolas:本当ですか?それは40文字です。これは、文書化されていないランタイムAPIのビットを使用するよりもはるかに "痛い"ようです。 –

+0

これは文字数の問題ではなく、「非乾燥」コードをクラスに追加するという問題です。 'ScalaRuntime'の解決策は完全ではなく、明らかに生産準備ができていませんが、DRYの方向に向いています。私は最初にtoStringを書き換えることと比較していました。(Tal Pressmanの答えを参照してください) – Nicolas

関連する問題