Singleton types for literals in Scala

It’s sometimes useful in Scala to have a type with a single value. These are called singleton types, and they show up most easily in the context of Scala’s objects. For example, if we have the following definition:

object foo {
  def whatever = 13

We can refer to a type foo.type that is the singleton type for foo—i.e., the type that contains nothing except foo. We can use this type to write a function that won’t compile with any non-foo argument:

def fooIdentity(x: foo.type) = x

For example:

scala> fooIdentity(foo)
res1: foo.type = foo$@5da19724

scala> fooIdentity("foo")
<console>:14: error: type mismatch;
 found   : String("foo")
 required: foo.type

Note that this error message doesn’t just tell us that we provided a String when we needed a foo—it lists the type as String("foo"). This is because string literals—like all other literals in Scala (except function literals)—are also singletons in the sense that their most specific type is a singleton type.

Unfortunately Scala doesn’t provide the .type syntax for literals—we can’t write "foo".type, for example (except in a compiler fork by Paul Phillips). Macro Paradise’s type macros get pretty close by allowing us to write something like singleton("foo") to refer to this type, but the 2.11 release is half a year away, and there’s no guarantee it will include type macros, anyway. We can get what we want while we’re waiting, though, if we’re willing to pay a bit of syntax tax.

We can start with the following macro:

import scala.language.experimental.macros
import scala.reflect.macros.Context

class LiteralSingleton[In <: Singleton] { type T = In }

object LiteralSingleton {
  def apply_impl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A]) = {
    import c.universe._

    def `new`(t: Type) = Apply(Select(New(TypeTree(t)), nme.CONSTRUCTOR), Nil)

    a.tree match {
      case Literal(const: Constant) => c.Expr[LiteralSingleton[_]](
            ConstantType(const) :: Nil
      case _ => c.abort(c.enclosingPosition, "Not a literal!")

This isn’t paradise, but it is code you can copy and paste into your 2.10 REPL today. Next we can define a method for lifting values into LiteralSingleton:

def ^[A](a: A) = macro LiteralSingleton.apply_impl[A]

And then:

scala> val thirteen = ^(13)
thirteen: LiteralSingleton[Int(13)] = LiteralSingleton@6ae26e57

scala> type _13 = thirteen.T
defined type alias _13

scala> def only13(x: _13): Int = x
only13: (x: _13)Int

scala> only13(14)
<console>:16: error: type mismatch;
 found   : Int(14)
 required: _13
    (which expands to)  Int(13)

scala> only13(13)
res0: Int = 13

The type alias is just a syntactic convenience—we could just as easily refer to thirteen.T directly in our method definitions, etc.

We can also write a macro that will give us the (unique) inhabitant of a singleton type:

case class Inhabitant[A <: Singleton](a: A)

object Inhabitant { 
  implicit def witness[A <: Singleton] = macro witness_impl[A]

  def witness_impl[A <: Singleton: c.WeakTypeTag](c: Context) = {
    import c.universe._

    val value = c.weakTypeOf[A].normalize match {
      case ConstantType(const) => Literal(const)
      case SingleType(pre, sym) => Select(TypeTree(pre), sym)
      case _ => c.abort(c.enclosingPosition, "Not a singleton type!")

          Select(reify(Inhabitant).tree, newTermName("apply")),
          TypeTree(c.weakTypeOf[A]) :: Nil
        value :: Nil

And then:

def inhabitant[A <: Singleton](implicit h: Inhabitant[A]) = h.a

Now we can write the following:

scala> inhabitant[foo.type].whatever
res1: Int = 13

scala> val w = ^("whatever")
w: LiteralSingleton[String("whatever")] = LiteralSingleton@5992f82e

scala> inhabitant[w.T]: String
res2: String = whatever

scala> inhabitant[_13]: Int
res3: Int = 13

For a concrete example of how singleton types for literals can be used in Scala, see Alois Cochard’s Shona library and this ScalaDays 2013 presentation by Ismael Juma and Alois. Shona uses type macros instead of the approach I’ve described here, but I’ve put together a proof-of-concept port to 2.10 that only needs def macros.