Во-первых, когда вы пишете, что неявное noneq2
имеет тип TypeOr[E, F]
, вы теряете уточнение типа https://typelevel.org/blog/2015/07/19/forget-refinement-aux.html. Правильно
implicit def noneq2[E, F](implicit ev: E =:!= F) = new TypeOr[E, F] {
type T = (E, F)
}
или лучше с явным типом
implicit def noneq2[E, F](implicit ev: E =:!= F): TypeOr[E, F] { type T = (E, F) } = new TypeOr[E, F] {
type T = (E, F)
}
Именно поэтому обычно вводится Aux
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)
}
}
Во-вторых, автоматически выводимый тип result
, т.е. Error[TypeOr[Int, String]#T, String]
(проекция типа TypeOr[Int,String]#T
- это супертип (y.T forSome { val y: TypeOr[Int, String] })
и, более того, x.T
), слишком грубый https://typelevel.org/blog/2015/07/23/type-projection.html
Лучше написать зависимый от пути тип для result
.
Но
val x = implicitly[TypeOr[Int, String]]
val result: Error[x.T, String] =
Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
не компилируется.
Дело в том, что implicitly
может повредить уточнения типа https://typelevel.org/blog/2014/01/18/implicitly_existential.html
Вот почему существует макрос 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
В качестве альтернативы можно указать пользовательский материализатор
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