Search code examples
scalapolymorphismsingleton

Scala: using Nothing for singleton instances of polymorphic types


Given a polymorphic trait like

 trait Transform[T] { def apply( t: T ) : T }

one might like to implement various specialized instances, such as

 case class Add[Double] extends Transform[Double] { def apply( t: Double ) ... }
 case class Append[String] extends Transform[String] { def apply( t: String ) ... }

etc. Now a frequently desired transform is also the identity transform. Instead of specializing identity for each type T, it appears preferable to use just one singleton instance for all types T. My question is: what is the best way to accomplish this in Scala?

Here is what I found so far: looking at how List[T] implements List.empty[T] and Nil, I tried using Nothing as the type T. This seems to make sense, since Nothing is a subtype of every other type:

 object Identity extends Transform[Nothing] {
    def apply( t: Nothing ) = t
 }

This seems to work. However, wherever I then want use this instance as-is, like here:

 val array = Array[Transform[String]]( Transform.Identity )

I get the compiler error "type mismatch; found: Identity.type, required: Transform[String]". In order to use it, I have to explicitly cast it:

 ... Identity.asInstanceOf[Transform[String]]

I am not sure this is the best or even the 'proper' way to do it. Thanks for any advice.


Solution

  • As @Kim Stebel points out your Transform[T] is invariant in T (and has to be because T occurs in both co- and contra- variant positions in def apply(t : T) : T) so Transform[Nothing] is not a subtype of Transform[String] and can't be made to be.

    If your main concern is the instance creation on each call of Kim's def Id[A] then your best model is the definition of conforms in in Predef,

    private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
    implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
    

    ie. use a polymorphic method, returning a singleton value cast to the appropriate type. This is one of occasions where erasure is a win.

    Applied to your situation we would have,

    object SingletonId extends Transform[Any] { def apply(t : Any) = t }
    def Id[A] = SingletonId.asInstanceOf[Transform[A]]
    

    Sample REPL session,

    scala> Id("foo")
    res0: java.lang.String = foo
    
    scala> Id(23)
    res1: Int = 23