简体   繁体   中英

Scala: specifying a generic type that is constructible from another

I would like to do something which, more or less, boils down to the following:

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

So that if I have:

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

I can write foo[Sx]("bar") to get Sx("foo") .

Is there any way to specify that an instance of a class should be constructible from an instance of another (in this example String) and to actually invoke the constructor in a generic way?

  1. You may use reflection (see @Ben Reich answer for detailed answer)

     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. Or a type class that can construct an instance:

     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 You would need an instance of the type class for every type you wish to construct:

     implicit val s1constructor = new CanConstruct[S1] { def apply(x:String) = S1(x) } ... 
  3. Also it seems to be the case for conversion functions:

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

Using reflection:

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

Which will return an Option[S] , if the proper constructor is found.

I sort of solved it with a macro:

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

I now can write things like:

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

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

It would be nice to find a way to avoid having to write the second type parameter, though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM