Selecting implicit conversion that is more specific

Hello,

Say I have a wrapper:

  sealed trait Val[V] {
    val v: V
  }

And I can create instances of these with:

  implicit def pack[A](s:A) : Val[A] = new Val[A] {
    override val v: A = s
  }

Now assume I create these instances:

    val r5: Val[String] = "100"
    val r6: Val[Double] = 200.0
    val r7: Val[Array[Double]] = Array(300.0, 301, 302)

Now here are two ways I can unpack the wrapped value v. If I have all
of the type information, I can use this definition:

    implicit def testFull[A,B](av: Val[A], f: A => B): Either[String,B] = {
      try {
        val v = av.v.asInstanceOf[A]
        Right(f(v))
      } catch {
        // When used via implicits should never reach this
        case e:Exception => Left(e.getMessage)
      }
    }

so:

    val u0 = testFull(r5, (e:String) => e.toInt)
    val u1 = testFull(r6, (e:Double) => e)
    val u2 = testFull(r7, (e:Array[Double]) => e)

and be sure that no run-time error will occur. On the other hand I
may also extract the wrapped value when not all the type information
is available, using the following definition:

    implicit def test[A,B,X](av: Val[X], f: A => B): Either[String,B] = {
      try {
        val v = av.v.asInstanceOf[A]
        Right(f(v))
      } catch {
        // When used via implicits should never reach this
        case e:Exception => Left(e.getMessage)
      }
    }

For example, if I have this data:

    val rs = Map("x" -> r5, "y" -> r6, "z" -> r7)
    val s5 = rs("x")
    val s6 = rs("y")
    val s7 = rs("z")

I can unpack the values so:

    val v0 = test(s5, (e:String) => e.toInt)
    val v1 = test(s6, (e:Double) => e)
    val v2 = test(s7, (e:Array[Double]) => e)

Naturally, this can fail during run-time.

So my question is, is it possible in Scala to select test or testFull
automatically so that the bound types are as specific as possible. For
example, if the compiler sees this:

    val c2 = choose(r6, (e:String) => e.toInt)

it automatically selects testFull. However, in the following case:

    val c2 = choose(s6, (e:String) => e.toInt)

it falls back onto test.

I have tried to use prioritized implicit conversion with these
definitions:


  sealed trait LowPriority {

    implicit def testFull[A,B](av: Val[A], f: A => B): Either[String,B] = {
      try {
        val v = av.v.asInstanceOf[A]
        Right(f(v))
      } catch {
        // When used via implicits should never reach this
        case e:Exception => Left(e.getMessage)
      }
    }
  }

  object HighPriority extends LowPriority {
    implicit def test[A,B,X](av: Val[X], f: A => B): Either[String,B] = {
      try {
        val v = av.v.asInstanceOf[A]
        Right(f(v))
      } catch {
        // When used via implicits should never reach this
        case e:Exception => Left(e.getMessage)
      }
    }
  }

  def choose[A,B,X](av: Val[X], f: A => B)(implicit a: (Val[X], A=>B) => Either[String,B]): Either[String,B] = {
     a(av, f)
  }

but the following code always executes:

    val c2 = choose(r6, (e:String) => e.toInt)

Seems like test is always selected irrespective of the priority set.

TIA

What exactly would be gained when you fall back to the unsafe testFull anyway? It doesn’t affect the return type.

Just a side-note, testFull is the safe version.

True

I am reading data from a text stream that must be converted to the appropriate type.
So here I am limited to the unsafe version. However once I extract the stream’s
elements (Val[X] with X known) I would like to use the compiler to check that no
typing error occurs after that.

Hope that makes sense.

Right, sorry, my mistake.

I think what you probably want to do is make testFull the default e.g. by putting it in the Val companion object, and add test to a separate object called unsafe. Then in the places in your code where you need the unsafe conversion you can import unsafe._ and the rest of your code will remain type-safe.

Yep, that would be the easy way to do it :wink:

I was hoping some compiler magic could avoid this manual selection.
I am working on a library and would like to make things easier for the users.

Thanks

I think that if you automate it, the conversion will always automatically fall back to the unsafe version, also in parts of your code where you have the full types available. If you want to keep most of your code safe, you’ll need some way to explicitly say that in some small part of your code you want to allow unsafety.

Yes, the experiments that I did shows this to be true. I was not sure if I
was doing something wrong. Originally I was hoping that the default would
be the more specific (safe) definition. When this failed the next unsafe
version would be used.

Of course I do not know if it is possible to check if one definition is more
or less specific, hence setting the priorities.

Thanks again for the help.