2

I have been looking into the details of shapeless singletons and have encountered a small example that doesn't work as expected. I thought that as long as we pass a singleton type to a method, there should be an implicit Witness.Aux[_] available in scope:

import shapeless._
import syntax.singleton._

object SingletonTest extends App {

  def check[K](a: K)(implicit witness: Witness.Aux[K]): Unit = {
    println(witness.value)
  }

  val b = 'key.narrow
  val c: Witness.`'key`.T = b

  check(c) // Works!
  check(b) /* Fails: shapeless.this.Witness.apply is not a valid implicit value                  for shapeless.Witness.Aux[Symbol with  shapeless.tag.Tagged[String("key")]]    because:
                 hasMatchingSymbol reported error: Type argument Symbol with shapeless.tag.Tagged[String("key")] is not a singleton type */
}

I would expect the types of b and c in the example to be the same (and checking it with =:= succeeds). If I add an implicit Witness[Witness.'key.T] into scope manually, the code compiles.

Environment: Scala 2.11.8; Shapeless 2.3.0

1 Answers1

3

The explanation is that the Scala compiler will widen the singleton type on the right hand side when inferring the type of the val on the left ... you'll find chapter and verse in the relevant section of the Scala Language Specification.

Here's an example of the same phenomenon independent from shapeless,

scala> class Foo ; val foo = new Foo
defined class Foo
foo: Foo = Foo@8bd1b6a

scala> val f1 = foo
f1: Foo = Foo@8bd1b6a

scala> val f2: foo.type = foo
f2: foo.type = Foo@8bd1b6a

As you can see from the definition of f2 the Scala compiler knows that the value foo has the more precise type foo.type (ie. the singleton type of val foo), however, unless explicitly requested it won't infer that more precise type. Instead it infers the non-singleton (ie. widened) type Foo as you can see in the case of f1.

See also this related answer.

Community
  • 1
  • 1
Miles Sabin
  • 23,015
  • 6
  • 61
  • 95