Is it possible to define a function return type based on a defined mapping from the type of a function argument?

StackOverflow https://stackoverflow.com/questions/23517053

Question

Ideally I'd like to be able to do the following in Scala:

import Builders._

val myBuilder = builder[TypeToBuild] // Returns instance of TypeToBuildBuilder
val obj = myBuilder.methodOnTypeToBuildBuilder(...).build()

In principle the goal is simply to be able to 'map' TypeToBuild to TypeToBuildBuilder using external mapping definitions (i.e. assume no ability to change these classes) and leverage this in type inferencing.

I got the following working with AnyRef types:

import Builders._

val myBuilder = builder(TypeToBuild)
myBuilder.methodOnTypeToBuildBuilder(...).build()

object Builders {
    implicit val typeToBuildBuilderFactory =
        new BuilderFactory[TypeToBuild.type, TypeToBuildBuilder]
    def builder[T, B](typ: T)(implicit ev: BuilderFactory[T, B]): B = ev.create
}

class BuilderFactory[T, B: ClassTag] {
    def create: B = classTag[B].runtimeClass.newInstance().asInstanceOf[B]
}

Note that the type is passed as a function argument rather than a type argument.

I'd be supremely happy just to find out how to get the above working with Any types, rather than just AnyRef types. It seems this limitation comes since Singleton types are only supported for AnyRefs (i.e. my use of TypeToBuild.type).

That being said, an answer that solves the original 'ideal' scenario (using a type argument instead of a function argument) would be fantastic!

EDIT

A possible solution that requires classOf[_] (would really love not needing to use classOf!):

import Builders._

val myBuilder = builder(classOf[TypeToBuild])
myBuilder.methodOnTypeToBuildBuilder(...).build()

object Builders {
    implicit val typeToBuildBuilderFactory =
        new BuilderFactory[classOf[TypeToBuild], TypeToBuildBuilder]
    def builder[T, B](typ: T)(implicit ev: BuilderFactory[T, B]): B = ev.create
}

class BuilderFactory[T, B: ClassTag] {
    def create: B = classTag[B].runtimeClass.newInstance().asInstanceOf[B]
}

Being able to just use builder(TypeToBuild) is really just a win in elegance/brevity. Being able to use builder[TypeToBuild] would be cool as perhaps this could one day work (with type inference advancements in Scala):

val obj: TypeToBuild = builder.methodOnTypeToBuildBuilder(...).build();

Here is a complete, working example using classOf: http://ideone.com/94rat3

Was it helpful?

Solution 3

I'll resort to answering my own question since a Redditor ended up giving me the answer I was looking for and they appear to have chosen not to respond here.

trait Buildable[T] {
  type Result
  def newBuilder: Result
}

object Buildable {
  implicit object ABuildable extends Buildable[A] {
    type Result = ABuilder
    override def newBuilder = new ABuilder
  }
  implicit object BBuildable extends Buildable[B] {
    type Result = BBuilder
    override def newBuilder = new BBuilder
  }
}

def builder[T](implicit B: Buildable[T]): B.Result = B.newBuilder

class ABuilder {
  def method1() = println("Call from ABuilder")
}

class BBuilder {
  def method2() = println("Call from BBuilder")
}

Then you will get:

scala> builder[A].method1()
Call from ABuilder

scala> builder[B].method2()
Call from BBuilder

You can see the reddit post here: http://www.reddit.com/r/scala/comments/2542x8/is_it_possible_to_define_a_function_return_type/

And a full working version here: http://ideone.com/oPI7Az

OTHER TIPS

Yes, Scala supports return types based on the parameters types. An example of this would be methods in the collections API like map that use the CanBuildFrom typeclass to return the desired type.

I'm not sure what you are trying to do with your example code, but maybe you want something like:

trait Builder[-A, +B] {
    def create(x: A): B
}

object Builders {
    implicit val int2StringBuilder = new Builder[Int, String] {
      def create(x: Int) = "a" * x
    }
    def buildFrom[A, B](x: A)(implicit ev: Builder[A, B]): B = ev.create(x)
}

import Builders._

buildFrom(5)

The magic with newInstance only works for concrete classes that have a constructor that takes no parameters, so it probably isn't generic enough to be useful.

If you're not afraid of implicit conversions, you could do something like this:

import scala.language.implicitConversions

trait BuilderMapping[TypeToBuild, BuilderType] {
    def create: BuilderType
}

case class BuilderSpec[TypeToBuild]()

def builder[TypeToBuild] = BuilderSpec[TypeToBuild]

implicit def builderSpecToBuilder[TypeToBuild, BuilderType]
    (spec: BuilderSpec[TypeToBuild])
    (implicit ev: BuilderMapping[TypeToBuild, BuilderType]) = ev.create




case class Foo(count: Int)

case class FooBuilder() {
    def translate(f: Foo) = "a" * f.count
}

implicit val FooToFooBuilder = new BuilderMapping[Foo, FooBuilder] {
  def create = FooBuilder()
}

val b = builder[Foo]
println(b.translate(Foo(3)))

The implicit conversions aren't too bad, since they're constrained to these builder-oriented types. The conversion is needed to make b.translate valid.

It looked like wingedsubmariner's answer was most of what you wanted, but you didn't want to specify both TypeToBuild and BuilderType (and you didn't necessarily want to pass a value). To achieve that, we needed to break up that single generic signature into two parts, which is why the BuilderSpec type exists.

It might also be possible to use something like partial generic application (see the answers to a question that I asked earlier), though I can't put the pieces together in my head at the moment.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top