Scala:指定可从另一个构造的泛型类型

时间:2015-02-22 16:45:57

标签: scala

我想做一些或多或少的事情归结为以下几点:

def foo[S](x: String): S = S(x)     // S(x) does not compile

如果我有:

case class S1(x:String)
case class S2(x:String)
...
case class Sn(x:String)

我可以写foo[Sx]("bar")来获取Sx("foo")

有没有办法指定一个类的实例应该可以从另一个实例(在本例中为String)中构造,并以通用方式实际调用构造函数?

3 个答案:

答案 0 :(得分:2)

  1. 您可以使用反射(请参阅@Ben Reich的答案以获得详细答案)

    def foo[S:ClassTag](x: String): S = { 
      val runtimeClass = implicitly[ClassTag[S]].runtimeClass
      val constructor = runtimeClass.<get a constructor with single String argument>
      constructor(x) .asInstanceOf[S]
    }
    
  2. 或者可以构造实例的类型类:

    trait CanConstruct[S] {
      def apply(x:String):S
    }
    
    def foo[S:CanConstruct](x: String): S = { 
      val constructor = implicitly[CanConstruct[S]]
      constructor(x).asInstanceOf[S]
    }
    

    UPD 您希望构建的每种类型都需要类型类的实例:

    implicit val s1constructor = new CanConstruct[S1] { def apply(x:String) = S1(x) }
    ...
    
  3. 转换函数似乎也是如此:

    implicit val convertStringToS1 = S1(_)
    implicit val convertStringToS2 = S2(_)
    ...
    

答案 1 :(得分:2)

使用反射:

import reflect.ClassTag

def foo[S: ClassTag](x: String) = implicitly[ClassTag[S]]
    .runtimeClass
    .getConstructors
    .map(a => a -> a.getParameters)
    .collectFirst { 
        case (constructor, Array(p)) if p.getType == classOf[String] 
            => constructor.newInstance(x).asInstanceOf[S] 
    }

如果找到合适的构造函数,将返回Option[S]

答案 2 :(得分:0)

我用宏来解决它:

object Construct {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  def construct[A,B](x:B):A  = macro constructImpl[A,B]

  def constructImpl[A: c.WeakTypeTag,B](c:Context)(x:c.Expr[B]) = {
    import c.universe._
    c.Expr[A](q"""new ${c.weakTypeOf[A]}(${x.tree})""")
  }
}

我现在可以写下这样的内容:

case class Foo(x:String)
case class Bar(x:Int)

construct[Foo,String]("foo")
construct[Bar,Int](42)

尽管如此,找到避免编写第二个类型参数的方法会很好。