0

I'd like to do some calculation on type level during runtime. So I define wrapper classes and implicit definitions for them. But I could not understand why type information is lost during computation

sealed trait Solve[In] {
  type Out
}

implicit def idSolve[I] = new Solve[I] {
  override type Out = I
}

type X = Int
val y = implicitly[Solve[X]]

val e = implicitly[X =:= y.Out]
val l = implicitly[X <:< y.Out]
val g = implicitly[y.Out <:< X]

The compiler accepts neither of e, l, g:

TypeSolution.scala:15: error: Cannot prove that test.Test.X =:= test.Test.y.Out.
  val e = implicitly[X =:= y.Out]
                    ^
TypeSolution.scala:16: error: Cannot prove that test.Test.X <:< test.Test.y.Out.
  val l = implicitly[X <:< y.Out]
                    ^
TypeSolution.scala:17: error: Cannot prove that test.Test.y.Out <:< test.Test.X.
  val g = implicitly[y.Out <:< X]
                    ^
three errors found

What is going on and why compiler refuses to admit that X and y.Out are the same types. Is it possible to rephrase the example so it would compile?

ayvango
  • 5,867
  • 3
  • 34
  • 73
  • I don't know much about implicitly and type equality, but in your definition of Solve type In and Out have no relation. I don't think compiler can go all the way to assume that for y instance In and Out are the same. But I repeat, just my two cents here. Have you tried replacing Out with In type in your definition of Solve? Like sealed trait Solve[In] { type In}. – Vincenzo Maggio Sep 09 '17 at 23:48
  • `val y` is a stable path. So the compiler is completely certain of its type. It is solved at compiler time – ayvango Sep 09 '17 at 23:58
  • It is simplified example. Actually my code is pretty complicated and I could not provide stable relation like Solve[In] {type Out = Int}. I have bunch of implicits conversions that forms *if-then-else* expression on compile time – ayvango Sep 10 '17 at 00:00
  • @VincenzoMaggio, you're incorrect in saying that this can't work. `idSolve` returns an inferred refinement type `Solve[I] { type Out = I }`, which you can explicitly annotate, and the compiler knows that if `val x = idSolve[Int]`, `x.Out = Int` (test it). There's a whole library based on this stuff (shapeless). There's no reason for the compiler to not know, as a refined type is no different from an ordinary one (modulo structural reflection hackery). – HTNW Sep 10 '17 at 03:41
  • Sorry @HTWN, I've already said I don't know much about this stuff, I was only trying to be useful. – Vincenzo Maggio Sep 10 '17 at 11:35

1 Answers1

3

implicitly "forgets" type information, as per it's definition (+ inferred types % renaming):

def implicitly[A](implicit a: A): A = a

Note that it returns something of type A, not a.type. Therefore, your code looks like:

val y = implicitly[Solve[Int]]
// ===
val y: Solve[Int] /* per return type of implicitly */ = implicitly[Solve[Int]]

y's type is inferred to Solve[Int], not Solve[Int] { type Out = Int }, so y.Out is unknown.

You can define a custom implicitly without this restriction:

import Predef.{ implicitly => _, _ } // Begone, failure!

import language.experimental.macros
import reflect.macros.whitebox.Context

def implicitly[T](implicit found: T): T = macro implicitly_impl[T]

def implicitly_impl[T: c.WeakTypeTag](c: Context)(found: c.Tree) = found
// We just return the exact same tree that we got, eliding the implicitly completely and dropping it's type-erasing touch.

Which works as a drop-in replacement:

scala> :paste
// Entering paste mode (ctrl-D to finish)

import Predef.{ implicitly => _, _ }

import language.experimental.macros
import reflect.macros.whitebox.Context

def implicitly[T](implicit found: T): T = macro implicitly_impl[T]

def implicitly_impl[T: c.WeakTypeTag](c: Context)(found: c.Tree) = found

// Exiting paste mode, now interpreting.

import Predef.{implicitly=>_, _}
import language.experimental.macros
import reflect.macros.whitebox.Context
defined term macro implicitly: [T](implicit found: T)T
implicitly_impl: [T](c: scala.reflect.macros.whitebox.Context)(found: c.Tree)(implicit evidence$1: c.WeakTypeTag[T])c.Tree

scala> :paste
// Entering paste mode (ctrl-D to finish)

sealed trait Solve[In] {
  type Out
}

implicit def idSolve[I] = new Solve[I] {
  override type Out = I
}

type X = Int
val y = implicitly[Solve[X]]

val e = implicitly[X =:= y.Out]
val l = implicitly[X <:< y.Out]
val g = implicitly[y.Out <:< X]

// Exiting paste mode, now interpreting.

defined trait Solve
idSolve: [I]=> Solve[I]{type Out = I}
defined type alias X
y: Solve[X]{type Out = X} = $anon$1@611f28f5
e: y.Out =:= y.Out = <function1>
l: X <:< X = <function1>
g: y.Out <:< y.Out = <function1>

Sidenote:

def implicitly[A](implicit a: A): a.type = a

Won't work, because Scala doesn't like it when you use singleton types without an AnyRef upper bound.

def implicitly[A <: AnyRef](implicit a: A): a.type = a

Works in this case, but it won't allow AnyVal subclasses or the like. However, the macro solution is not very complicated and works for everything, which is a fair trade.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • is possible to change predef project-wise? – ayvango Sep 10 '17 at 10:57
  • I trick that I really like is having multiple package declarations at the top of a file. `package a; package b` is similar to `package a.b`, but it imports from both `a` and `b`, while the latter only imports from `b`. I generally have one line for the project package itself; then a few more based on how packages group together. If you define `implicitly` in `package object proj`, then in any file that goes `package proj; package ...`, `proj.implicitly` will shadow `Predef.implicitly`. – HTNW Sep 10 '17 at 13:21
  • I create custom package `mymacros`, put `Imports` trait inside and declare `package object a extends mymacros.Imports`? I think it a little bit better then global declaration – ayvango Sep 13 '17 at 12:43
  • I believe that's fine. – HTNW Sep 13 '17 at 14:19