10

I'm observing a very strange type error with shapeless.everywhere. Consider the following ammonite script which loads fine with load.module:

load.ivy("com.chuusai" %% "shapeless" % "2.3.0")

@

import shapeless._
import poly._

final case class Person(name: Person.Name, age: Person.Age)

object Person {
  final case class Name(value: String) extends AnyVal
  final case class Age(value: Int) extends AnyVal
}

def happyBirthday(person: Person, howManyYearsPast: Int): Person = {
  object incAge extends ->((age: Int) => age + howManyYearsPast)
  // THE MAGIC VAL
  val oldPerson = everywhere(incAge)(person)
  oldPerson
}

val john = Person(Person.Name("John Doe"), Person.Age(42))

val oldJohn = happyBirthday(john, 30)

Now if I try to “inline” the MAGIC VAL in this script, i.e. replace the val with just everywhere(incAge)(person), I get the following type error out of nowhere:

Main.scala:50: type mismatch;
 found   : person.type (with underlying type cachef6f1545a8d4dc31cb54d9957675f0559.Person)
 required: shapeless.poly.Case[_1.type,shapeless.HNil]{type Result = ?} where val _1: shapeless.EverywhereAux[incAge.type]
  everywhere(incAge)(person)

WAT?

I guess there's scalac's black magic implicit resolution to blame, but can't me no sense of what's going on here. It'd be great (and definitely somewhat enlightening to me) if someone could unravel this mystery to me.

Thanks

  • Do you get the same result when compiling as a source file with scalac, or only on the REPL (Ammonite or otherwise)? – Miles Sabin Apr 19 '16 at 21:44
  • @MilesSabin Well, I didn't try this made-up example with scalac tbh. But it's a stripped down version of a real issue in our code so I presume it'll fail with scalac as well. I'll try scalac tomorrow. –  Apr 19 '16 at 21:57

1 Answers1

4

I don't know the real reason for this issue, but it is caused by the expected type influencing implicit search and type inference. You can make the non-inlined version not compile by providing an expected type:

def happyBirthday(person: Person, howManyYearsPast: Int): Person = {
  object incAge extends ->((age: Int) => age + howManyYearsPast)
  val oldPerson: Person = everywhere(incAge)(person)
  oldPerson
}

This fails with the exact same error.

On the other hand, you can make the inlined version compile by removing the expected type (aka return type) from the function:

def happyBirthday(person: Person, howManyYearsPast: Int) = {
  object incAge extends ->((age: Int) => age + howManyYearsPast)
  everywhere(incAge)(person)
}

I'm pretty sure one could consider this a "bug", however, type inference is unspecified, so it's going to take a while to figure out what exactly should happen.

larsrh
  • 2,579
  • 8
  • 30
  • So, yes, this fixes the type error, but I'm still curious how a type bound for exactly the type that's supposed to be inferred can break type inference that badly… but then again, it's scala, so I probably shouldn't dare to ask ;p –  Apr 20 '16 at 12:36