How can I chain implicits in Scala? - scala

The pimp-my-library pattern allows me to seemingly add a method to a class by making available an implicit conversion from that class to one that implements the method.
Scala does not allow two such implicit conversions taking place, however, so I cannot got from A to C using an implicit A to B and another implicit B to C. Is there a way around this restriction?

Scala has a restriction on automatic conversions to add a method, which is that it won't apply more than one conversion in trying to find methods. For example:
class A(val n: Int)
class B(val m: Int, val n: Int)
class C(val m: Int, val n: Int, val o: Int) {
def total = m + n + o
}
// This demonstrates implicit conversion chaining restrictions
object T1 { // to make it easy to test on REPL
implicit def toA(n: Int): A = new A(n)
implicit def aToB(a: A): B = new B(a.n, a.n)
implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n)
// won't work
println(5.total)
println(new A(5).total)
// works
println(new B(5, 5).total)
println(new C(5, 5, 10).total)
}
EDIT: View bounds ('<%') are deprecated since Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (You can use type classes instead)
However, if an implicit definition requires an implicit parameter itself(View bound), Scala will look for additional implicit values for as long as needed. Continue from the last example:
// def m[A <% B](m: A) is the same thing as
// def m[A](m: A)(implicit ev: A => B)
object T2 {
implicit def toA(n: Int): A = new A(n)
implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)
// works
println(5.total)
println(new A(5).total)
println(new B(5, 5).total)
println(new C(5, 5, 10).total)
}
"Magic!", you might say. Not so. Here is how the compiler would translate each one:
object T1Translated {
implicit def toA(n: Int): A = new A(n)
implicit def aToB(a: A): B = new B(a.n, a.n)
implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n)
// Scala won't do this
println(bToC(aToB(toA(5))).total)
println(bToC(aToB(new A(5))).total)
// Just this
println(bToC(new B(5, 5)).total)
// No implicits required
println(new C(5, 5, 10).total)
}
object T2Translated {
implicit def toA(n: Int): A = new A(n)
implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)
// Scala does this
println(bToC(5)(x => aToB(x)(y => toA(y))).total)
println(bToC(new A(5))(x => aToB(x)(identity)).total)
println(bToC(new B(5, 5))(identity).total)
// no implicits required
println(new C(5, 5, 10).total)
}
So, while bToC is being used as an implicit conversion, aToB and toA are being passed as implicit parameters, instead of being chained as implicit conversions.
EDIT
Related question of interest:
A discussion on types, origin and precedence of implicits

Note that you can build circles with implicit parameters, too. Those are, however, detected by the compiler, as exhibited by this:
class Wrap {
class A(implicit b : B)
class B(implicit c : C)
class C(implicit a : A)
implicit def c = new C
implicit def b = new B
implicit def a = new A
}
The error(s) given to the user are not as clear as they could be, though; it just complains could not find implicit value for parameter for all three construction site. That might obscure the underlying problem in less obvious cases.

Here's a code that also accumulates the path.
import scala.language.implicitConversions
// Vertices
case class A(l: List[Char])
case class B(l: List[Char])
case class C(l: List[Char])
case class D(l: List[Char])
case class E(l: List[Char])
// Edges
implicit def ad[A1 <% A](x: A1) = D(x.l :+ 'A')
implicit def bc[B1 <% B](x: B1) = C(x.l :+ 'B')
implicit def ce[C1 <% C](x: C1) = E(x.l :+ 'C')
implicit def ea[E1 <% E](x: E1) = A(x.l :+ 'E')
def pathFrom(end:D) = end
pathFrom(B(Nil)) // res0: D = D(List(B, C, E, A))

Related

Scala: implicitly convert Option to Option of implicit class [duplicate]

I'm trying to write a function which re-uses the implicit conversions which I have for Object A -> Object B when they are wrapped in an Option in a generic way so that Option[A] -> Option[B] conversions also work.
What I've come up with is:
implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
This works when I assign a Some(..) to a value but not when I assign an Option val; see the following console output:
scala> trait T
defined trait T
scala> case class Foo(i: Int) extends T
defined class Foo
scala> case class Bar(i: Int) extends T
defined class Bar
scala> implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
fromFooToBar: (f: Foo)Bar
scala> implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
fromBarToFoo: (b: Bar)Foo
scala> implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
fromOptionToOption: [A, B](from: Option[A])(implicit conversion: (A) => B)Option[B]
scala> val foo: Option[Foo] = Some(Bar(1))
foo: Option[Foo] = Some(Foo(1))
// THIS WORKS as expected
scala> val fooOpt = Some(Foo(4))
fooOpt: Some[Foo] = Some(Foo(4))
scala> val barOpt2: Option[Bar] = fooOpt
<console>:16: error: type mismatch;
found : Some[Foo]
required: Option[Bar]
val barOpt2: Option[Bar] = fooOpt
^
//THIS FAILS.
I don't really see the difference between the first and second conversion. Somehow it doesn't invoke the implicit conversion in the latter. I guess it has something to do with the type system, but I can't see how just yet. Any ideas?
-Albert
(I'm on scala 2.9.1)
Here's clue:
scala> val fooOpt: Option[Bar] = Option(Foo(1))
fooOpt: Option[Bar] = Some(Bar(1))
And another:
scala> implicit def foobar(x: String): Int = augmentString(x).toInt
foobar: (x: String)Int
scala> val y: Option[String] = Option(1)
y: Option[String] = Some(1)
scala> val y: Option[Int] = Option("1")
y: Option[Int] = Some(1)
Looks like a legitimately odd bug. I'd pop open a smaller test case and open an issue (or search for one in JIRA).
As an aside:
You could use some category theory to handle lots of different types of "Option-ish" things.
package object fun {
trait Functor[Container[_]] {
def fmap[A,B](x: Container[A], f: A => B): Container[B]
}
object Functor {
implicit object optionFunctor extends Functor[Option] {
override def fmap[A,B](x: Option[A], f: A => B): Option[B] = x map f
}
// Note: With some CanBuildFrom magic, we can support Traversables here.
}
implicit def liftConversion[F[_], A, B](x: F[A])(implicit f: A => B, functor: Functor[F]): F[B] =
functor.fmap(x,f)
}
That's a bit more advanced, as you're mapping some category theory FP onto the problem, but it's a more general solution to lift implicit conversations into containers as needed. Notice how they chain by using one implicit conversation method that takes a more limited implicit argument.
ALSO, this should make the examples work:
scala> val tmp = Option(Foo(1))
tmp: Option[Foo] = Some(Foo(1))
scala> val y: Option[Bar] = tmp
y: Option[Bar] = Some(Bar(1))
And make your usage of Some more dangerous:
scala> val tmp = Some(Foo(1))
tmp: Some[Foo] = Some(Foo(1))
scala> val y: Option[Bar] = tmp
<console>:25: error: could not find implicit value for parameter functor: fun.Functor[Some]
val y: Option[Bar] = tmp
^
That's telling you that variance is critical, and interacts with implicits. My guess is you ran into a very rare, probably hard to fix bug that can be avoided using other techniques.
You might not be aware of it, but there's a flag for that: -Xlog-implicits. And this is what it says:
scala> val barOpt2: Option[Bar] = fooOpt
fromOptionToOption is not a valid implicit value for Some[Foo] => Option[Bar] because:
incompatible: (from: Option[Foo])(implicit conversion: Foo => B)Option[B] does not match expected type Some[Foo] => Option[Bar]
<console>:16: error: type mismatch;
found : Some[Foo]
required: Option[Bar]
val barOpt2: Option[Bar] = fooOpt
^
And there you go -- it doesn't know what type B must be. 0__ mentioned that this problem doesn't happen with invariant collections, and that makes some sense. In invariant collections, B must be exactly Bar, while for covariant collections it could be any subtype of Bar.
So, why does val foo: Option[Foo] = Some(Bar(1)) work? Well, there's a flag for that too... -Ytyper-debug. Not for the weak, however, given the extreme verbosity.
I waddled through anyway, comparing what happens in both cases, and the answer is rather simple... it's not the Option that is being converted in that case, but Bar! Remember, you declared an implicit conversion from Bar => Foo, so it is applying that conversion before passing the result to Some!
It doesn't work because the Scala Language Specification defines view as follows:
Implicit parameters and methods can also define implicit conversions called views. A view from type S to type T is defined by an implicit value which has function type S=>T or (=>S)=>T or by a method convertible to a value of that type.
fromOptionToOption doesn't conform to the three categories since it takes an implicit parameter. Compiler doesn't seem to find converter with both destination and source having generic type.
Defining a view from Option[Foo] to Option[Bar] works as expected.
trait T
case class Foo(i: Int) extends T
case class Bar(i: Int) extends T
object Main {
implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
// implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] =
// from.map(conversion(_))
implicit def fromOptionFooToOptionBar(o: Option[Foo]): Option[Bar] = o map { foo => foo }
def test(): Option[Bar] = {
val fooOpt = Some(Foo(4))
val barOpt2: Option[Bar] = fooOpt
barOpt2
}
}
println(Main.test)
Running this prints out:
$ scala so.scala
Some(Bar(4))
However, all is not lost. It's not as nice as general Option to Option, but we can do something like anything that can turn into Bar to Option[Bar] by view bound.
trait T
case class Foo(i: Int) extends T
case class Bar(i: Int) extends T
object Main {
implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
implicit def fromOptionToOptionBar[A <% Bar](from: Option[A]): Option[Bar] =
from map { foo => foo }
def test(): Option[Bar] = {
val fooOpt = Some(Foo(4))
val barOpt2: Option[Bar] = fooOpt
barOpt2
}
}
println(Main.test)
Here's another workaround that can be used for general Option to Option but requires extra .convert call:
trait T
case class Foo(i: Int) extends T
case class Bar(i: Int) extends T
case class Converter[A](x: Option[A]) {
def convert[B](implicit ev: Function1[A, B]): Option[B] = x map { a: A => ev(a) }
}
object Main {
implicit def optionToConverter[A](x: Option[A]) = Converter(x)
implicit def fooToBar(x: Foo) = Bar(x.i)
def test(): Option[Bar] = {
val fooOpt = Some(Foo(4))
val barOpt: Option[Bar] = fooOpt.convert
barOpt
}
}
println(Main.test)
Indeed it's a very strange problem. I tried to use another type than Option, and it turns out that the problem is that Option is covariant in its type parameter. This works all:
case class A[B](value: B) // invariant in B
case class X()
case class Y()
implicit def xtoy(x: X): Y = Y()
implicit def ytox(x: Y): X = X()
implicit def movea[U, V](from: A[U])(implicit view: U => V): A[V] = A[V](from.value)
def test(a: A[Y]) = "ok"
test(A(X())) // (1)
val f = A(X())
test(f) // (2)
But if instead I define A as
case class A[+B](value: B) // covariant in B
The case (2) fails. Case (1) always succeeds, because Scala already converts X to Y before wrapping it in an A.
Now that we know the problem source, you need to wait for a type guru to explain why this is actually a problem... The conversion is still valid, you see:
askForY(movea(f)) // succeeds, even with A[+B]
I improved #jseureth answer and added support for Traversable:
trait Mappable[A, B, C[_]] {
def apply(f: A => B): C[B]
}
package object app {
implicit class OptionMappable[A, B, C[X] <: Option[X]](option: C[A]) extends Mappable[A, B, Option] {
override def apply(f: A => B): Option[B] = option.map(f)
}
implicit class TraversableMappable[A, B, C[X] <: Traversable[X]](traversable: C[A])
(implicit cbf: CanBuildFrom[C[A], B, C[B]]) extends Mappable[A, B, C] {
override def apply(f: A => B): C[B] = {
val builder = cbf(traversable)
builder.sizeHint(traversable)
builder ++= traversable.map(f)
builder.result()
}
}
implicit def liftConversion[C[_], A, B](x: C[A])
(implicit f: A => B, m: C[A] => Mappable[A, B, C]): C[B] = m(x)(f)
}
Now you can implicitly convert options and traversables:
implicit def f(i: Int): String = s"$i"
val a: Option[String] = Some(1)
val b: Seq[String] = Seq(1, 2, 3)

Chain implicit conversion of collection

I fail make an implicit conversion List[A] => List[B], given implicit conversion A => B.
There is a very related question that has a solution but does not work for me. Also, here is a nice documentation for chaining implicits, which I used as the base of my code below.
The point I try to make in the code below is that chaining implicits works fine in all expected cases, even for collection-like objects like the Container below, but fails for collections:
object ChainImplicits extends App{
case class A(n: Int)
implicit def toA(n: Int): A = A(n)
case class B(m: Int, n: Int)
implicit def aToB[T](a: T)(implicit f: T => A): B = B(a.n, a.n)
case class C(m: Int, n: Int, o: Int) {
def total = m + n + o
}
implicit def bToC[T](b: T)(implicit f: T => B): C = C(b.m, b.n, b.m + b.n)
// works
println(5.total)
println(new A(5).total)
println(new B(5, 5).total)
println(new C(5, 5, 10).total)
case class Container[T](value:T) {
def map[B](f: T => B) = Container(f(value))
}
implicit def ContainerConv[A,B](container:Container[A])
(implicit f: A => B): Container[B] = container.map(f)
val container = Container(1)
container.value.total //Works, as expected
def containerCTotal(containerC: Container[C]) = containerC.value.total
containerCTotal(container) //Works too!
implicit def listConv[A,B](collection: List[A])
(implicit f: A => B): List[B] = collection.map(f)
val list = List(1)
def CTotals(list: List[C]) = list.map(_.total)
CTotals(listConv(list)) //Explicit conversion works, finds the chain implicit conversions Int => C :)
CTotals(list) //... but implicit does not :(
def ATotals(list: List[A]) = list.map(_.total)
ATotals(list) // Simple (non-chained) conversion of contained values does not work either :(
}
How can I make these last conversions work?
I also tried the (deprecated) view bounds, getting the same results:
implicit def convert[B, A <% B](l: List[A]): List[B] = l map { a => a: B }
Just in case, the compilation error is the expected:
type mismatch;
found : List[Int]
required: List[ChainImplicits.C]
CTotals(list) //... but implicit does not :(
And similarly for ATotals(list)
I tried it on scala versions 2.11.8 and 2.11.4.
UPDATE:
I confirmed after Andreas' comment that the underlying issue has something to do with the List being covariant. Giving covariance to the Container class above (i.e. Container[+A]) makes the previously working implicit conversion at containerCTotal(container) to fail.

Scala: Lower type bound A without B

I want to achieve this:
implicit def aConvertable(obj: A): B = new B(obj)
implicit def aConvertableWithoutB[T <: A without B](obj: T): C = new C(obj)
What is the correct way to say: "A without B"?
I'm assuming that B is a subtype of A. The trick is to introduce an implicit "AllowedConversion" trait that works for all A's, and then two implicit instances for B that have the same type, so that they're ambiguous for B:
object Foo {
trait A
class B(a: A) extends A
class C(a: A)
class AllowedConversion[T]
implicit def allowedForAs[T<:A] = new AllowedConversion[T]
implicit def contradictoryForBs[T <: B] = new AllowedConversion[T]
implicit def contradictoryForBs2[T <: B] = new AllowedConversion[T]
implicit def aConvertable(obj: A): B = new B(obj)
implicit def aConvertableWithoutB[T <: A](obj: T)(implicit allowed: AllowedConversion[T]): C = new C(obj)
// error!
// new B(new A{}):C
// ok!
new A{}:C
}

View bounds and ambiguous implicit conversion error

This is a much simpler version of my earlier post ambiguous implicit conversion errors
Here is a code snippet from the post How can I chain implicits in Scala?
class A(val n: Int)
class B(val m: Int, val n: Int)
class C(val m: Int, val n: Int, val o: Int) {
def total = m + n + o
}
object T2 {
implicit def toA(n: Int): A = new A(n)
implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)
// works
println(5.total)
println(new A(5).total)
println(new B(5, 5).total)
println(new C(5, 5, 10).total)
}
Now if I add a class D and implicit def dToC as follows, I get errors at locations shown in the snippet.
class A(val n: Int)
class B(val m: Int, val n: Int)
class D(val m: Int, val n: Int) //Added by me
class C(val m: Int, val n: Int, val o: Int) {
def total = m + n + o
}
object T2 {
implicit def toA(n: Int): A = new A(n)
implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)
implicit def dToC[D1 <% D](d: D1): C = new C(d.m, d.n, d.m + d.n) //Added by me
println(5.total) //Ambiguous implicit conversion error
println(new A(5).total) //Ambiguous implicit conversion error
println(new B(5, 5).total) //Ambiguous implicit conversion error
println(new C(5, 5, 10).total)
}
I don't understand how both bToC and dToC are possible conversion functions for say println(new B(5, 5).total).
View bounds obviously get translated to an extra implicit function parameter. So both bToC and dToC get translated to [T](x: T)(implicit f: T => Something): C
The issue being that the compiler disregards the presence/absence of implicit arguments in the ambiguity check.
case class Foo[A](value: A)
implicit def foo[A](x: A)(implicit ev: Any =:= String) = Foo(x)
implicit def bar[A](x: A)(implicit ev: Any =:= Int) = Foo(x)
1.value //Ambiguous though none are applicable
(1) Int=>A=>B=>C
(2) Int=>A=>D=>C
2 paths can be chosen, and the compiler don't know which path to go, so that's why ambiguous implicit conversion error was raised.

Enforce type difference

In Scala I can enforce type equality at compile time. For example:
case class Foo[A,B]( a: A, b: B )( implicit ev: A =:= B )
scala> Foo( 1, 2 )
res3: Foo[Int,Int] = Foo(1,2)
scala> Foo( 1, "2" )
<console>:10: error: Cannot prove that Int =:= java.lang.String.
Is there a way to enforce that type A and type B should be different ?
I have a simpler solution, which also leverages ambiguity,
trait =!=[A, B]
implicit def neq[A, B] : A =!= B = null
// This pair excludes the A =:= B case
implicit def neqAmbig1[A] : A =!= A = null
implicit def neqAmbig2[A] : A =!= A = null
The original use case,
case class Foo[A,B](a : A, b : B)(implicit ev: A =!= B)
new Foo(1, "1")
new Foo("foo", Some("foo"))
// These don't compile
// new Foo(1, 1)
// new Foo("foo", "foo")
// new Foo(Some("foo"), Some("foo"))
Update
We can relate this to my "magical typesystem tricks" (thanks #jpp ;-) as follows,
type ¬[T] = T => Nothing
implicit def neg[T, U](t : T)(implicit ev : T =!= U) : ¬[U] = null
def notString[T <% ¬[String]](t : T) = t
Sample REPL session,
scala> val ns1 = notString(1)
ns1: Int = 1
scala> val ns2 = notString(1.0)
ns2: Double = 1.0
scala> val ns3 = notString(Some("foo"))
ns3: Some[java.lang.String] = Some(foo)
scala> val ns4 = notString("foo")
<console>:14: error: No implicit view available from
java.lang.String => (String) => Nothing.
val ns4 = notString2("foo")
^
Riffing off of Jean-Philippe's ideas, this works:
sealed class =!=[A,B]
trait LowerPriorityImplicits {
implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
implicit def nequal[A,B](implicit same: A =:= B = null): =!=[A,B] =
if (same != null) sys.error("should not be called explicitly with same type")
else new =!=[A,B]
}
case class Foo[A,B](a: A, b: B)(implicit e: A =!= B)
Then:
// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))
// doesn't compile
// Foo(1f, 1f)
// Foo("", "")
I'd probably simplify this as follows, since the checks for "cheating" can always be circumvented anyway (e.g. Foo(1, 1)(null) or =!=.nequal(null)):
sealed class =!=[A,B]
trait LowerPriorityImplicits {
/** do not call explicitly! */
implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
/** do not call explicitly! */
implicit def nequal[A,B]: =!=[A,B] = new =!=[A,B]
}
I liked the simplicity and effectiveness of Miles Sabin's first solution, but was a bit dissatisfied with the fact that the error we get is not very helpful:
By example with the following definition:
def f[T]( implicit e: T =!= String ) {}
Attemtping to do f[String] will fail to compile with:
<console>:10: error: ambiguous implicit values:
both method neqAmbig1 in object =!= of type [A]=> =!=[A,A]
and method neqAmbig2 in object =!= of type [A]=> =!=[A,A]
match expected type =!=[String,String]
f[String]
^
I'd rather have the compiler tell me something along the line of "T is not different from String"
It turns out that it's quite easy if add yet another level of implicits in such a way that we turn the ambiguity error
into an implicit not found error. From then we can use the implicitNotFound annotation to emit a custom error message:
#annotation.implicitNotFound(msg = "Cannot prove that ${A} =!= ${B}.")
trait =!=[A,B]
object =!= {
class Impl[A, B]
object Impl {
implicit def neq[A, B] : A Impl B = null
implicit def neqAmbig1[A] : A Impl A = null
implicit def neqAmbig2[A] : A Impl A = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A =!= B = null
}
Now let's try to call f[String]:
scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
f[String]
^
That's better. Thanks compiler.
As a last trick for those that like the context bound syntactic sugar, one can define this alias (based on type lambdas):
type IsNot[A] = { type λ[B] = A =!= B }
Then we can define f like this:
def f[T:IsNot[String]#λ] {}
Whether it is easier to read is highly subjective. In any case is definitly shorter than writing the full implicit parameter list.
UPDATE: For completeness, here the equivalent code for expressing that A is is not a sub-type of B:
#annotation.implicitNotFound(msg = "Cannot prove that ${A} <:!< ${B}.")
trait <:!<[A,B]
object <:!< {
class Impl[A, B]
object Impl {
implicit def nsub[A, B] : A Impl B = null
implicit def nsubAmbig1[A, B>:A] : A Impl B = null
implicit def nsubAmbig2[A, B>:A] : A Impl B = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A <:!< B = null
}
type IsNotSub[B] = { type λ[A] = A <:!< B }
And for expressing that A is not convertible to B :
#annotation.implicitNotFound(msg = "Cannot prove that ${A} <%!< ${B}.")
trait <%!<[A,B]
object <%!< {
class Impl[A, B]
object Impl {
implicit def nconv[A, B] : A Impl B = null
implicit def nconvAmbig1[A<%B, B] : A Impl B = null
implicit def nconvAmbig2[A<%B, B] : A Impl B = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A <%!< B = null
}
type IsNotView[B] = { type λ[A] = A <%!< B }
Based on Landei's idea, the following seems to work:
case class Foo[A, B <: A, C <: A]( a: B, b: C)(implicit f: AnyVal <:< A)
scala> Foo(1f, 1.0)
res75: Foo[AnyVal,Float,Double] = Foo(1.0,1.0)
scala> Foo("", 1.0)
res76: Foo[Any,java.lang.String,Double] = Foo(,1.0)
scala> Foo(1f, 1f)
<console>:10: error: Cannot prove that AnyVal <:< Float.
Foo(1f, 1f)
^
scala> Foo("", "")
<console>:10: error: Cannot prove that AnyVal <:< java.lang.String.
Foo("", "")
^
scala> Foo("", 1)
res79: Foo[Any,java.lang.String,Int] = Foo(,1)
Here's another attempt:
class =!=[A, B] private () extends NotNull
object =!= {
implicit def notMeantToBeCalled1[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
implicit def notMeantToBeCalled2[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
implicit def unambigouslyDifferent[A, B](implicit same: A =:= B = null): =!=[A, B] =
if (same != null) error("should not be called explicitly with the same type")
else new =!=
}
case class Foo[A, B](a: A, b: B)(implicit ev: A =!= B)
Then, again:
// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))
// doesn't compile
// Foo(1f, 1f)
// Foo("", "")
Like in my other proposal, the aim here is to introduce a compile-time ambiguity when A and B are the same. Here, we provide two implicits for the case where A is the same as B, and an unambiguous implicit when this is not the case.
Note that the problem is that you could still explicitly provide the implicit parameter by manually calling =!=.notMeantToBeCalled1 or =!=.unambigouslyDifferent. I couldn't think of a way to prevent this at compile time. However, we can throw an exception at runtime, with the trick that unambigouslyDifferent requires an evidence parameter itself indicating whether A is the same as B. But wait... Aren't we trying to prove the exact opposite? Yes, and that's why that same implicit parameter has a default value of null. And we expect it to be null for all legal uses — the only time where it would not be null is when a nasty user calls e.g. Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float]), and there we can prevent this cheating by throwing an exception.
How about something like this, then?
class Foo[A, B] private (a: A, b: B)
object Foo {
def apply[A, B <: A, C >: A <: B](a: A, b: B)(implicit nothing: Nothing) = nothing
def apply[A, B >: A, C >: B <: A](a: A, b: B)(implicit nothing: Nothing, dummy: DummyImplicit) = nothing
def apply[A, B](a: A, b: B): Foo[A, B] = new Foo(a, b)
}
Then:
// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))
// doesn't compile
// Foo(1f, 1f)
// Foo("", "")
The idea is to make resolution ambiguous when Ais the same as B, and unambiguous when they are not the same. To further emphasize that the ambiguous methods should not be called, I added an implicit of type Nothing, which should never be around (and should certainly look wrong to the caller if they try to insert one explicitly). (The role of the DummyImplicit is just to give a different signature to the first two methods.)
This is not an answer, just the beginnings of what I could think is an answer. The code below will return either an Yes or an No depending on whether the types are equal or not, if you ask for implicitly[AreEqual[A,B]]. How to go from there to actually making a check I haven't been able to figure out. Maybe the whole approach is doomed, maybe someone can make something out of it. Mind you, implicitly[No[A, B]] will always return something, one can't use that. :-(
class AreEqual[A, B]
trait LowerPriorityImplicits {
implicit def toNo[A : Manifest, B : Manifest]: No[A, B] = No[A, B]
}
object AreEqual extends LowerPriorityImplicits {
implicit def toYes[A, B](implicit ev: A =:= B, m1: Manifest[A], m2: Manifest[B]): Yes[A, B] = Yes[A, B]
}
case class Yes[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
override def toString: String = "Yes(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}
case class No[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
override def toString: String = "No(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}
Test:
scala> implicitly[AreEqual[String, Option[String]]]
res0: AreEqual[String,Option[String]] = No(java.lang.String, scala.Option[java.lang.String])
scala> implicitly[AreEqual[String, String]]
res1: AreEqual[String,String] = Yes(java.lang.String, java.lang.String)