2016-01-19 4 views
7

Ich habe einige Fallklassen, in deren Begleitobjekt eine Methode tupled definiert ist. Wie aus dem unten stehenden Code in Begleitobjekten hervorgeht, handelt es sich lediglich um eine Code-Duplizierung.Definieren eines Merkmals, das um die Fallklasse in der Scala erweitert werden soll

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 
} 

Aus einer anderen Frage (can a scala self type enforce a case class type), scheint es, wie wir nicht die Selbst Typ eines Merkmals ein Fall Klasse sein erzwingen kann.

Gibt es eine Möglichkeit, ein Merkmal zu definieren (z. B. Tupled), das wie folgt angewendet werden kann?

// 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 { 
} 

Antwort

12

Weil es keine Beziehung zwischen FunctionN Typen in Scala, ist es nicht möglich, dies zu tun, ohne arity-Ebene vorformulierten irgendwo-es gibt einfach keine Möglichkeit, zu abstrahieren, über den Begleiter Objekte apply Methoden, ohne alle möglichen Zahlen von Aufzählen Mitglieder.

Sie könnten dies mit der Hand mit einer Reihe von CompanionN[A, B, C, ...] Eigenschaften tun, aber das ist ziemlich nervig. Shapeless bietet eine viel bessere Lösung, die Sie so etwas wie die folgenden schreiben können:

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)) 
} 

Und dann:

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] 

, die Sie mögen diese verwenden:

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) 

Das könnte dir nicht einmal den CaseClassCompanion Teil, da es möglich ist, eine generische Methode zu konstruieren, die Tupel in Fallklassen umwandelt (unter der Annahme, dass die Mitgliedstypen in einer Linie liegen):

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] 

Und dann:

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) 

Dies wird mit bis zu 22 Mitgliedern für Fallklassen arbeiten (da die apply Methode auf dem Begleitobjekt kann nicht auf eine Funktion eta-erweitert werden, wenn es mehr als 22 Argumente).

+2

Als eine Nebenbemerkung, 'fromTuple' könnte ein besserer Name für diese Methode sein - es ist wahr, dass Sie das' apply' tupling, aber Aufruf des Ergebnisses 'tupled' macht klingt wie die Umwandlung ist _to_ ein Tupel statt Weg von_. –

+0

Vielen Dank !!! – TheKojuEffect

+0

@TravisBrown Wie kann ich mit Fallklassen von 1 Parameter den gleichen Effekt erzielen? – vicaba

2

Das Problem ist, dass die Signatur von apply von Fall zu Fall unterscheidet, und dass es keine gemeinsame Eigenschaft für diese Funktionen gibt. Book.tupled und Author.tupled haben grundsätzlich den gleichen Code, haben aber sehr unterschiedliche Signaturen. Daher ist die Lösung möglicherweise nicht so schön, wie wir es gerne hätten.


Ich kann mir einen Weg vorstellen, der ein Anmerkungs-Makro verwendet, um den Textbaustein auszuschneiden. Da es keine gute Möglichkeit gibt, dies mit der Standardbibliothek zu tun, werde ich auf die Codegenerierung zurückgreifen (die immer noch kompilierungsfähig ist). Der Nachteil hierbei ist, dass Annotationsmakros die Verwendung des macro paradise Compiler-Plugins erfordern. Makros müssen sich auch in einer separaten Kompilierungseinheit befinden (wie ein anderes sbt-Unterprojekt). Code, der die Annotation verwendet, würde auch die Verwendung des Macro Paradise Plugins erfordern.

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) 
    } 

} 

Verbrauch:

@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> 

Alternativ so etwas wie dieses kann mit formlos möglich sein. Siehe @ TravisBrowns bessere Antwort.

+0

Danke für Ihre Antwort. – TheKojuEffect