0

Consider this code:

  trait TypeOr[E, F] {
    type T
  }

  implicit def noneq2[E, F](implicit ev: E =!= F): TypeOr[E, F] = new TypeOr[E, F] {
    type T = (E, F)
  }

  sealed trait Error[+E, +A]

  case class Err[E, A](e: Error[E, A]) {
    def combine[B, F](f: A => Error[F, B])(implicit ev: TypeOr[E, F]): Error[ev.T, B] = ???
  }
  val result = Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])

So far so good. From the definitions above, I concluded, that the expanded type of the result is following:

  val itsType: Error[(Int, String), String] = result

But apparently it is not, since the compiler replies with:

 found   : returnerror.Comb.Error[returnerror.Comb.TypeOr[Int,String]#T,String]
 required: returnerror.Comb.Error[(Int, String),String]
  val itsType: Error[(Int, String), String] = result

Is it possible to find out the simplified - expanded type of the expression? I can't get this information from compiler, I tried to print the AST before the erasure phase, but the expanded type is still not there.

ryskajakub
  • 6,351
  • 8
  • 45
  • 75
  • 3
    can you also provide code for `returnerror.TypeOr` ? – Bogdan Vakulenko Apr 10 '19 at 09:51
  • 3
    The compiler doesn't believe that `TypeOr` and `Tuple2` are equivalent. Should they be? – jwvh Apr 10 '19 at 10:03
  • 1
    [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Dmytro Mitin Apr 10 '19 at 11:05
  • I'm specifically interested in seeing, what the compiler thinks those types are more than just solving this particular case. I tried to print compiler phases, but it doesn't print the expanded type in any phase. – ryskajakub Apr 10 '19 at 14:47
  • @coubeatczech Try to add `scalacOptions in Compile ++= Seq("-Xprint-types", "-Xprint:typer")` to build.sbt. – Dmytro Mitin Apr 10 '19 at 15:00
  • Or you can enter expession in REPL and it will print type and value of this expression. Or you can use scala-reflect. `import scala.reflect.runtime.universe._` `def printType[A: TypeTag](a: A) = println(typeOf[A])` – Dmytro Mitin Apr 10 '19 at 15:04
  • Or `println(typeOf[A].dealias)`. – Dmytro Mitin Apr 10 '19 at 15:07

1 Answers1

1

Firstly, when you write that implicit noneq2 has type TypeOr[E, F] you lost type refinement https://typelevel.org/blog/2015/07/19/forget-refinement-aux.html . Correct is

implicit def noneq2[E, F](implicit ev: E =:!= F) = new TypeOr[E, F] {
  type T = (E, F)
}

or better with explicit type

implicit def noneq2[E, F](implicit ev: E =:!= F): TypeOr[E, F] { type T = (E, F) }  = new TypeOr[E, F] {
  type T = (E, F)
}

That's the reason why usually type Aux is introduced

object TypeOr {
  type Aux[E, F, T0] = TypeOr[E, F] { type T = T0 }

  implicit def noneq2[E, F](implicit ev: E =:!= F): Aux[E, F, (E, F)] = new TypeOr[E, F] {
    type T = (E, F)
  }
}

Secondly, automatically inferred type of result i.e.Error[TypeOr[Int, String]#T, String] (type projection TypeOr[Int,String]#T is a supertype of (y.T forSome { val y: TypeOr[Int, String] }) and moreover of x.T) is too rough https://typelevel.org/blog/2015/07/23/type-projection.html

It's better to write path-dependent type for result.

But

val x = implicitly[TypeOr[Int, String]]
val result: Error[x.T, String] =
  Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])

doesn't compile.

The thing is that implicitly can damage type refinements https://typelevel.org/blog/2014/01/18/implicitly_existential.html

That's the reason why there exists macro shapeless.the.

val x = the[TypeOr[Int, String]]
val result: Error[x.T, String] = Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
val itsType: Error[(Int, String), String] = result

Alternatively, custom materializer can be defined

object TypeOr {
  //...
  def apply[E, F](implicit typeOr: TypeOr[E, F]): Aux[E, F, typeOr.T] = typeOr
}

val x = TypeOr[Int, String]
val result: Error[x.T, String] =
  Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
val itsType: Error[(Int, String), String] = result
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • if you define `def a: Unit = result` - which is obviously wrong type, how do you force the compiler the expanded type it expects? – ryskajakub Apr 10 '19 at 14:48
  • @coubeatczech What do you mean by "expand type"? Actually you can check that `def a: Unit = result` compiles so it's correct type. In Scala value of any type can be tranformed to `Unit` type. – Dmytro Mitin Apr 10 '19 at 14:56
  • I didn't know that, thanks. So let's say it would be `def a: Int = result`. By expanded type I mean for `type T = String` to show `String` and not `T` - resolving the name of the type until there is nothing more to resolve. In my example, the `#T` refers to some type and I want to resolve it. – ryskajakub Apr 10 '19 at 16:34
  • @coubeatczech Try `import scala.reflect.runtime.universe._` `def printType[A: TypeTag](a: A) = println(typeOf[A].dealias)` `printType(result)` – Dmytro Mitin Apr 10 '19 at 16:46
  • i will get: `src/main/scala/returnerror/Error.scala:49: No TypeTag available for ev.T` – ryskajakub Apr 10 '19 at 17:28
  • I will try to add a complete example to the question – ryskajakub Apr 10 '19 at 17:29
  • @coubeatczech This means that `T` is not a type alias, it's just an abstract type. – Dmytro Mitin Apr 10 '19 at 17:30
  • Like `trait TypeOr[A, B] { type T }`. – Dmytro Mitin Apr 10 '19 at 17:31