如何做通用元组 ->Scala 中的案例类转换? [英] How to do generic tuple -> case class conversion in Scala?

查看:42
本文介绍了如何做通用元组 ->Scala 中的案例类转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设有人提供了一个函数:

Let's say someone provided a function:

def getTupleData[T](source: String): List[T] = {
  // ...
}

我需要编写一个函数,它接受一个case类C作为类型参数,并在上述函数的帮助下返回List[C].这是我到目前为止所得到的:

I need to write a function which takes a case class C as the type parameter and return List[C] with the help of the above function. Here is what I have got so far:

def getCaseClassData[C](source: String): List[C] = {
  // Somehow get T from C.
  // For example, if C is case class MyCaseClass(a: Int, b: Long), then T is (Int, Long)
  // How to get T?      

  getTupleData[T](source) map { tuple: T =>
    // Somehow convert tuple into a case class instance with the case class type parameter
    // C.tupled(tuple) ??  Type parameter cannot be used like this. :(
  }
}

更具体地说,在我看来,我在这里问了两个问题:

More specifically, it seems to me I'm asking two questions here:

  1. 如何从表示 case 类的类型参数中显式获取元组的类型,以便将其用作类型参数?
  2. 如何在不知道案例类的实际名称而只知道类型参数的情况下从元组实例创建案例类实例?

推荐答案

您找不到任何相当简单或直接的方法来做到这一点.如果您已准备好接受更复杂的解决方案,请耐心等待.

You won't find any reasonably simple or direct way to do it. If you' re ready for the more involved solutions, bear with me.

每个 case 类在其伴随对象中都有一个 apply 方法,用于实例化该类.通过在此方法上调用 tupled(在 eta-expansion 之后),您将获得一个函数,该函数接受一个元组并创建相应的 case 类实例.

Every case class has an apply method in its companion object, which instantiates the class. By calling tupled on this method (after eta-expansion), you'll get a function that takes a tuple and creates the corresponding case class instance.

当然,问题是每个 case 类的 apply 都有不同的签名.我们可以通过引入一个代表 case 类工厂的类型类来解决这个问题,并通过一个宏提供这个类型类的实例(它只会委托给 case 类的 apply 方法).

Now of course the problem is that the every case class's apply has a different signature. We can get around this by introducing a type class representing a case class factory, and provide instances of this type class through a macro (which will just delegate to the case class's apply method).

import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros

trait CaseClassFactory[C,T]{
  type Class = C
  type Tuple = T
  def apply(t: Tuple): C
}

object CaseClassFactory {
  implicit def factory1[C,T]: CaseClassFactory[C,T] = macro factoryImpl[C,T]
  implicit def factory2[C]: CaseClassFactory[C,_] = macro factoryImpl[C,Nothing]
  def apply[C,T]: CaseClassFactory[C,T] = macro factoryImpl[C,T]
  def apply[C]: CaseClassFactory[C,_] = macro factoryImpl[C,Nothing]

  def factoryImpl[C:c.WeakTypeTag,T:c.WeakTypeTag](c: Context) = {
    import c.universe._
    val C = weakTypeOf[C]
    val companion = C.typeSymbol.companion match {
      case NoSymbol => c.abort(c.enclosingPosition, s"Instance of $C has no companion object")
      case sym      => sym
    }
    val tupledTree = c.typecheck(q"""($companion.apply _).tupled""")
    val T = tupledTree.tpe match {
      case TypeRef(_, _, List(argTpe, _)) => argTpe
      case t => c.abort(c.enclosingPosition, s"Expecting type constructor (Function1) for $C.tupled, but got $t: ${t.getClass}, ${t.getClass.getInterfaces.mkString(",")}")
    }
    if (! (c.weakTypeOf[T] <:< T)) {
      c.abort(c.enclosingPosition, s"Incompatible tuple type ${c.weakTypeOf[T]}: not a sub type of $T")
    }
    q"""
    new CaseClassFactory[$C,$T] {
      private[this] val tupled = ($companion.apply _).tupled
      def apply(t: Tuple): $C = tupled(t)
    }
    """
  }
}

有了它,你可以做这样的事情:

With it you can do something like this:

scala> case class Person(name: String, age: Long)
defined class Person

scala> val f = CaseClassFactory[Person]
f: CaseClassFactory[Person]{type Tuple = (String, Long)} = $anon$1@63adb42c

scala> val x: f.Tuple = ("aze", 123)
x: f.Tuple = (aze,123)

scala> implicitly[f.Tuple =:= (String, Long)]
res3: =:=[f.Tuple,(String, Long)] = <function1>

scala> f(("aze", 123))
res4: Person = Person(aze,123)

但更重要的是,您可以要求 CaseClassFactory 的实例作为隐式参数,从而允许对您的案例类进行一般实例化.然后,您可以执行以下操作:

But more importantly, you can require an instance of CaseClassFactory as an implicit parameter, allowing to generically instantiate your case classes. You can then do something like:

scala> implicit class TupleToCaseClassOps[T](val t: T) extends AnyVal {
     |   def toCaseClass[C](implicit f: CaseClassFactory[C,T]): C = {
     |     f(t)
     |   }
     | }
defined class TupleToCaseClassOps

scala> case class Person(name: String, age: Long)
defined class Person

scala> ("john", 21).toCaseClass[Person]
res5: Person = Person(john,21)

很整洁.有了这个类型类,getCaseClassData 就变成了:

Pretty neat. Armed with this type class, getCaseClassData then becomes:

def getCaseClassData[C](source: String)(implicit f: CaseClassFactory[C,_]): List[C] = {
  getTupleData[f.Tuple](source) map { tuple: f.Tuple =>
    f(tuple)
  }
}

这篇关于如何做通用元组 ->Scala 中的案例类转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆