Eduardo
Eduardo

Reputation: 8402

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?

Upvotes: 1

Views: 85

Answers (3)

Eduardo
Eduardo

Reputation: 8402

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.

Upvotes: 0

Ben Reich
Ben Reich

Reputation: 16324

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.

Upvotes: 2

Arseniy Zhizhelev
Arseniy Zhizhelev

Reputation: 2401

  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(_)
    ...
    

Upvotes: 2

Related Questions