2016-01-19 7 views
7

コンパイルオブジェクトに定義されたメソッドtupledを持ついくつかのケースクラスがあります。コンパニオンオブジェクトの下のコードから見ることができるように、それは単なるコードの重複です。別の質問(can a scala self type enforce a case class type)からケースクラスによって拡張される特性をスカラーで定義する

case class Book(id: Int, isbn: String, name: String) 

object Book { 
    def tupled = (Book.apply _).tupled // Duplication 
} 


case class Author(id: Int, name: String) 

object Author { 
    def tupled = (Author.apply _).tupled // Duplication 
} 

我々はケースクラスであることを形質の自己種類を強制することはできませんように、それはそうです。

以下のように適用できる形質(例:Tupled)を定義する方法はありますか?

// What would be value of ??? 
trait Tupled { 
    self: ??? => 

    def tupled = (self.apply _).tupled 
} 

// Such that I can replace tupled definition with Trait 
object Book extends Tupled { 
} 

答えて

12

ScalaではFunctionN種類の間には関係がありませんので、それはアリティレベルの決まり文句なしにこれを実行することはできませんどこか、そこのすべての可能な数字を列挙せずにコンパニオンオブジェクトのapplyの方法に比べて抽象化への道はただませんメンバー。

CompanionN[A, B, C, ...]個の特徴を手でこれを行うことができますが、それはかなり面倒です。

import shapeless.{ Generic, HList }, shapeless.ops.product.ToHList 

class CaseClassCompanion[C] { 
    def tupled[P <: Product, R <: HList](p: P)(implicit 
    gen: Generic.Aux[C, R], 
    toR: ToHList.Aux[P, R] 
): C = gen.from(toR(p)) 
} 

そして:

あなたはこのように使用することができます
case class Book(id: Int, isbn: String, name: String) 
object Book extends CaseClassCompanion[Book] 

case class Author(id: Int, name: String) 
object Author extends CaseClassCompanion[Author] 

scala> Book.tupled((0, "some ISBN", "some name")) 
res0: Book = Book(0,some ISBN,some name) 

scala> Author.tupled((0, "some name")) 
res1: Author = Author(0,some name) 

あなたがかもしれないShapelessでは、次のようなものを書くことができますはるかに優れたソリューションを提供し、 CaseClassCompanionの部分も必要ではありません。なぜなら、タプルをケースクラスに変換する一般的なメソッドを作ることができるからです(メンバータイプが並べられていると仮定します):

class PartiallyAppliedProductToCc[C] { 
    def apply[P <: Product, R <: HList](p: P)(implicit 
    gen: Generic.Aux[C, R], 
    toR: ToHList.Aux[P, R] 
): C = gen.from(toR(p)) 
} 

def productToCc[C]: PartiallyAppliedProductToCc[C] = 
    new PartiallyAppliedProductToCc[C] 

そして:

scala> productToCc[Book]((0, "some ISBN", "some name")) 
res2: Book = Book(0,some ISBN,some name) 

scala> productToCc[Author]((0, "some name")) 
res3: Author = Author(0,some name) 

がある場合、コンパニオンオブジェクト上のapply方法はETA-拡張機能にすることはできませんので、これは、最大22人のメンバー(とケースクラスのために動作します22以上の議論)。

+2

'fromTuple'はこのメソッドの方がいいかもしれません。' apply'をタップするのは本当ですが、 'tupled'という結果を呼び出すのは、変換が_to_タプル_from_の –

+0

ありがとうございました!!! – TheKojuEffect

+0

@TravisBrown 1つのパラメータのケースクラスで同じ効果を得るにはどうすればよいですか? – vicaba

2

applyのシグネチャはケースごとに異なり、これらの機能に共通する特性はないという問題があります。 Book.tupledAuthor.tupledは基本的に同じコードを持ちますが、非常に異なる署名を持っています。したがって、このソリューションは私たちが望むほどうまくいかないかもしれません。


アノテーションマクロを使用して定型文を切り取る方法が考えられます。標準のライブラリではそれができないので、コード生成(まだコンパイル時の安全性があります)に頼りましょう。ここでの注意点は、注釈マクロではmacro paradiseコンパイラプラグインを使用する必要があるということです。マクロは別のコンパイル単位(別のsbtサブプロジェクトのようなもの)にもなければなりません。注釈を使用するコードでは、マクロ・パラダイス・プラグインを使用する必要があります。

import scala.annotation.{ StaticAnnotation, compileTimeOnly } 
import scala.language.experimental.macros 
import scala.reflect.macros.whitebox.Context 

@compileTimeOnly("enable macro paradise to expand macro annotations") 
class Tupled extends StaticAnnotation { 
    def macroTransform(annottees: Any*): Any = macro tupledMacroImpl.impl 
} 

object tupledMacroImpl { 

    def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { 
    import c.universe._ 
    val result = annottees map (_.tree) match { 
     // A case class with companion object, we insert the `tupled` method into the object 
     // and leave the case class alone. 
     case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }") 
     :: (objDef @ q"object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => ..$objDefs }") 
     :: Nil if mods.hasFlag(Flag.CASE) => 
     q""" 
      $classDef 
      object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => 
      ..$objDefs 
      def tupled = ($objName.apply _).tupled 
      } 
     """ 
     case _ => c.abort(c.enclosingPosition, "Invalid annotation target: must be a companion object of a case class.") 
    } 

    c.Expr[Any](result) 
    } 

} 

使用法:


@Tupled 
case class Author(id: Int, name: String) 

object Author 


// Exiting paste mode, now interpreting. 

defined class Author 
defined object Author 

scala> Author.tupled 
res0: ((Int, String)) => Author = <function1> 
また、このようなものは、型崩れしてもよいです。 @ TravisBrownのより良い答えを見てください。

+0

あなたの答えをありがとう。 – TheKojuEffect

関連する問題