3
votes

TL;DR: I thought that if the following compiles:

implicitly[X => Y]

than so will this:

(??? :X) :Y

It turns out I was wrong. Backstory: I toyed with an implementation of type unions:

private[this] val cast = identity[Any] _

abstract class TypeUnionLevel4Implicits {
    implicit def implicitUnionUnification[L, R, U <: Any | Any](implicit left :L => U, right :R => U) :(L | R) => U =
        left.asInstanceOf[(L | R) => U]
}

sealed abstract class TypeUnionLevel3Implicits extends TypeUnionLevel4Implicits {
    implicit def implicitRightComposedUnionMember[X, L, R <: Any | Any](implicit right :X => R) :X => (L | R) =
        right.asInstanceOf[X => (L | R)]
}

sealed abstract class TypeUnionLevel2Implicits extends TypeUnionLevel3Implicits {
    implicit def implicitLeftComposedUnionMember[X, L <: Any | Any, R](implicit left :X => L) :X => (L | R) =
        left.asInstanceOf[X => (L | R)]
}

sealed abstract class TypeUnionLevel1Implicits extends TypeUnionLevel2Implicits {
    implicit def implicitRightUnionMember[L, R] :R => (L | R) = cast.asInstanceOf[R => (L | R)]
}

abstract class TypeUnionImplicits private[slang] extends TypeUnionLevel1Implicits {
    implicit def implicitLeftUnionMember[L, R] :L => (L | R) = cast.asInstanceOf[L => (L | R)]
}

object union extends TypeUnionImplicits {
    type |[+L, +R]
}

Testing:

implicitly[String => (String | Int)]
implicitly[String => (Int | String)]

implicitly[(String | Int) => (Int | String)]
implicitly[String => (Int | String | Double)]
implicitly[String => (String | Int | Double | Short)]
implicitly[String => (Short | (Double | (Int | String)))]
implicitly[(String | Int | Double | Short) => (Short | Double | Int | String)]

Compiles! Success! Or is it?

val left = "left" :String | Int
val right = "right" :Int | String

val swap = (left :String | Int) :Int | String
val middle = "middle" :Int | String | Double
val farLeft = "farLeft" :String | Int | Double | Short
val farRight = "farRight" :Short | (Double | (Int | String))
val shuffle = farLeft :Short | Double | Int | String

compile...compile...compile...

Information:(14, 19) typist.this.`package`.implicitUnionUnification is not a valid implicit value for String | Int => Int | String because:
not enough arguments for method implicitUnionUnification: (implicit left: L => U, right: R => U): L | R => U.
Unspecified value parameter right.
    val swap = (left :String | Int) :Int | String  
Error:(14, 19) type mismatch;
found   : String | Int
required: Int | String
    val swap = (left :String | Int) :Int | String

FFFFuuu....
I opened an issue at /dev/null because it surely must be a bug. But it looks just so plain and basic stuff, that it seems there must be a way to work around it. What I tried:

  • direct implicit conversions as normal methods, not function-returning ones;

  • an intermediate implicit class

       class TypeUnionMember[X, U]
    

    with the implicit methods above returning it instead of X=>U, together with one top level implicit providing X=>U when TypeUnionMember[X, U] implicit exists:

       implicit def widenToTypeUnion[X, U](implicit unify :TypeUnionMember[X, U]) :X => U = cast.asInstanceOf[X, U]
    

The latter proved interesting and by interesting I mean frustrating: the logs sad that widenToTypeUnion is an invalid implicit for String => String | Int because an implicit TypeUnionMember[String, Nothing] cannot be found. What?

I thought that if the following compiles: implicitly[X => Y] than so will this: (??? :X) :Y Nope. stackoverflow.com/questions/62630439/… stackoverflow.com/questions/62205940/… stackoverflow.com/questions/62751493/…Dmytro Mitin
Thanks yet again. This time however I already did exactly what you recomended in the first link: a special type class TypeUnionMember with only a single implicit conversion declaration based on it, as per the last snippet, and it didn't change anything at all. Or did you mean for type classes to completely replace the conversion, as in no way of automatically converting from one type to another in arbitrary places?Turin
I read the post by M.Odersky some time ago and repressed it, as the fact that I keep playing all the time with features marked for removal fills me with dread. My only hope is that there indeed will be 'better ways to do things' and more robust type unification.Turin
@user Variance of Is is incorrect. It should be Is[-A, +B]. With wrong variance some things compile while they shouldn't. For example implicitly[Any Is (String | Int)], implicitly[Any Is Nothing], implicitly[(Boolean | Double) Is (String | Int)].Dmytro Mitin