vikram-r vikram-r - 3 months ago 7
Scala Question

Using abstract type parameters with a factory function to generate new objects of the correct type

I have an abstract class that will be implemented in many different ways. I am trying to create a method that can take any one of these implementors as a parameter, and return a new instance of the same type. I am trying to implement this behavior by passing a "factory" function to the method that yields a new object of an abstract type (which will be set appropriately in each implementor).

I've tried to breakdown the problem I'm having into the following code:

My parent abstract class:

abstract class Parent(val x: Int) {
type Self <: Parent

def factory: Int ⇒ Self

def me = "Parent"
}


An example of a Child class:

class Child(x: Int) extends Parent(x) {
type Self = Child

override def factory: Int ⇒ Self = {
(v: Int) ⇒ new Child(v)
}

override def me = "Child"
}


I'm trying to use the
Self
type parameter as a way to ensure that the
factory
method generates an object of the correct type.

Now the method itself:

object Parent {
def transform[T <: Parent](input: T#Self, factory: (Int ⇒ T#Self)): T#Self = {
//do stuff with input
input.me
val result = 123
factory(result)
}
}


Now when I try to actually wire this all up:

class Transformer[T <: Parent] {

var everyone: List[T#Self] = List.empty

def start() = {
val updated = for (e ← everyone) yield {
Parent.transform[T](e, e.factory)
}

everyone = updated
}
}


I get a compile error when I try to pass the factory to the transform method

Type mismatch, expected (Int) => T#Self, actual (Int) => Parent.this.Self


I've tried a variety of things to get this to work, but no luck. I'm very new to this still, so it's possible (probably likely) I'm trying to do something crazy here. A better alternative would be greatly appreciated, but I'm still interested to see if it's possible to get something like this to work. The end goal is to have a way for the
transform
method to generate new instances of the exact same type that was provided as a parameter.

Any help is greatly appreciated. Thanks!

Answer

I'm trying to use the Self type parameter as a way to ensure that the factory method generates an object of the correct type.

This reminds me of tpolecat's Returning the "Current" Type in Scala:

I have a type hierarchy … how do I declare a supertype method that returns the “current” type?

We can adapt the F-Bounded Types approach discussed in that post to your Parent and Child hierarchy:

trait Parent[A <: Parent[A]] { this: A =>

  def x: Int

  def factory: Int ⇒ A

  def me = "Parent"
}

class Child(override val x: Int) extends Parent[Child] {

  override def factory = (v: Int) ⇒ new Child(v)

  override def me = "Child"
}

class OtherChild(override val x: Int) extends Parent[OtherChild] {

  override def factory = (v: Int) ⇒ new OtherChild(v)

  override def me = "OtherChild"
}

object Parent {
  def transform[A <: Parent[A]](input: A): A = {
    //do stuff with input
    input.me
    val result = 123
    input.factory(result)
  }
}

And then, following the Bonus Round: How do we deal with collections? section, your Transformer becomes something like this:

class Transformer {

  import scala.language.existentials

  var everyone = List[A forSome { type A <: Parent[A] }](new Child(1), new OtherChild(2))

  def start() = {
    val updated = everyone.map(Parent.transform(_))

    everyone = updated
  }
}