Vikram
Vikram

Reputation: 333

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!

Upvotes: 0

Views: 304

Answers (1)

danielnixon
danielnixon

Reputation: 4268

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

Upvotes: 1

Related Questions