dk14
dk14

Reputation: 22374

type-parametrized object in Scala

Is there a way to write object with generic parameters, like that:

object Aaa[T] {
   def f(a: T) = a
}

Or, in other words, to have singleton on instance-level, but not on the type level.

I know that I could do the same with:

object Aaa {
   def f[T](a: T) = a
}

But what if I have several methods to restrict with single polymorphic type:

object Aaa[T] {
   def f1(a: T) = a
   def f2(b: T) = b
}

//somewhere in the code:
val a = Aaa[Int]
import a._

f1(5)
f2(6)
someFunction(a)

P.S. All I want is singleton factory with type-parameter as input (and as a key). Usually it's implemented with Map[TypeTag, Object] (which requires thread-safety, btw) - looking for more nice solution here. For example with "parametrize method" approach I can't:

trait T1[T] {
   def f1(a: T): T
   def f2(b: T): T
}

object Aaa extends T1 { //won't compile, have to use class
   //some heavy initialization and data here
   (1 to 100500000).map(List.empty[T]) 
   def f1[T](a: T) = a
   def f2[T](b: T) = b
}

It might be some method that creates a big structure and requires generic type to be specified.

And of course this object may be passed to another function (or value), so single polymorphic type restriction really works.

Upvotes: 2

Views: 2402

Answers (1)

dk14
dk14

Reputation: 22374

Yes, that's possible using .asInstanceOf:

trait AaaImpl[T] {
   this: Aaa.type =>
   def f(a: T) = a
}

object Aaa extends AaaImpl[Nothing] { // lower bound of T should be here
   def apply[T] = this.asInstanceOf[AaaImpl[T]]
}

// Exiting paste mode, now interpreting.

defined trait AaaImpl
defined module Aaa

scala> Aaa[Int].f(5)
res7: Int = 5

scala> Aaa[Double].f(5.0)
res8: Double = 5.0

It's safe to cast here as long as your object doesn't do any other typecasts. asInstanceOf just copying your type (using AaaImpl[Nothing] as prototype) but with new type parameter (like case class's copy does in values world).

P.S. Trait's methods will be also available inside Aaa itself, but Nothing will be used for T

P.S.2 You may also implement some other traits to pass this object to some external library:

//External
trait SomeAbstractType[T] {
   def f(a: T): T
}

def ff[T](x: SomeAbstractType[T]) = x

//Internal
trait AaaImpl[T] { def f(a: T) = a }

object Aaa extends AaaImpl[Nothing] with SomeAbstractType[Nothing] { // lower bound of T should be here
   def apply[A] = this.asInstanceOf[AaaImpl[A] with SomeAbstractType[A]]
}

// Exiting paste mode, now interpreting.

defined trait SomeAbstractType
ff: [T](x: SomeAbstractType[T])SomeAbstractType[T]
defined trait AaaImpl
defined module Aaa

scala> ff(Aaa[Int])
res11: SomeAbstractType[Int] = Aaa$@6e18a830

scala> ff(Aaa[Double])
res12: SomeAbstractType[Double] = Aaa$@6e18a830 //same instance

scala> ff(Aaa[Int]).f(5) //different behaviour
res15: Int = 5

scala> ff(Aaa[Double]).f(5.0)
res16: Double = 5.0

Update1. Examples of something cooler than identity:

scala> trait AaaImpl[T] {
   def list(a: T) = List(a)
   def empty = List[T]()
   def square(a:T)(implicit n:Numeric[T]) = n.times(a, a)
}
defined trait AaaImpl

scala> object Aaa extends AaaImpl[Nothing]{ // lower bound of T should be here
   def apply[A] = this.asInstanceOf[AaaImpl[A]]
}
defined module Aaa

scala> Aaa[Int].list(5)
res21: List[Int] = List(5)

scala> Aaa[Int].empty
res22: List[Int] = List()

scala> Aaa[Int].square(5)
res23: Int = 25

scala> Aaa[List[Int]].square(5)
<console>:11: error: type mismatch;
 found   : Int(5)
 required: List[Int]
              Aaa[List[Int]].square(5)
                                    ^

Upvotes: 2

Related Questions