2016-01-19 3 views
7

У меня есть классы классов, которые имеют метод tupled, определенный в его компаньонном объекте. Как видно из приведенного ниже кода в сопутствующих объектах, это просто дублирование кода.Определить черту, которая будет расширена по классу case в scala

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 
} 

С другой вопрос (can a scala self type enforce a case class type), кажется, что мы не можем обеспечить соблюдение собственного типа признака быть случай класса.

Есть ли способ определить признак (скажем 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

Потому что нет никакой связи между FunctionN типами в Scala, это не возможно сделать это без Арности уровня шаблонного где-то нет никакого способа абстрагироваться над спутником объектами apply методов без перечисления все возможного номера члены.

Вы можете сделать это вручную с помощью связки CompanionN[A, B, C, ...] черт, но это довольно раздражает. Shapeless обеспечивает гораздо лучшее решение, которое позволяет написать что-то вроде следующего:

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) 

Вы могли бы даже не требуется часть CaseClassCompanion, так как можно построить общий метод, который преобразует кортежи в классы case (при условии, что типы членов выстраиваются в линию):

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) 

Это будет работать для конкретных классов с до 22 членов (с apply метода на объекте компаньона не может быть ета вспененного к функции, если есть более 22 аргументов).

+2

В качестве примечания, 'fromTuple' может быть лучшим названием для этого метода, это правда, что вы кортежи' apply', но называя результат 'tupled' делает звук, как преобразование является _to_ кортежа вместо of_from_. –

+0

Спасибо большое !!! – TheKojuEffect

+0

@TravisBrown Как я могу достичь такого же эффекта с классами case из 1 параметра? – vicaba

2

Проблема заключается в том, что подпись apply отличается от случая к случаю и что для этих функций нет общей черты. Book.tupled и Author.tupled в основном имеют одинаковый код, но имеют очень разные подписи. Поэтому решение может быть не таким приятным, как хотелось бы.


Я могу представить себе способ использования макроса аннотации, чтобы вырезать шаблон. Поскольку нет подходящего способа сделать это со стандартной библиотекой, я прибегну к генерации кода (который все еще сохраняет безопасность во время компиляции). Здесь необходимо указать, что макросы аннотации требуют использования компилятора macro paradise. Макросы также должны быть в отдельном модуле компиляции (например, в другом подпроекте sbt). Код, который использует аннотацию, также потребует использования плагина macro paradise.

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> 

С другой стороны, что-то подобное может быть возможным с бесформенным. См. Лучший ответ ТрэвисБроу.

+0

Спасибо за ваш ответ. – TheKojuEffect

Смежные вопросы