Reputation: 5023
trait Foo
trait Bar extends Foo
def doStuff[T <: Foo](x: T)(implicit ev: T =!:= Foo) = x
doStuff(new Foo{}) //ambiguous implicit value
doStuff(new Bar)// successful
Implicit resolution is happening on compilation time, so in here I think there may be two implicit value with exactly same type to trigger ambiguous stuff.
Right now, I am going to introduce shapeless into the team, my colleagues think this ambiguous implicit is not ideal, and I dont have strong argument about it. Is this only way to do it in order to make type safe in scala. If it is, what can I do to customize the error message ?
Edit:
In the shapeless, I want to make the sum of 2 NAT not equal to 7, I can code like this to make compilation fail.
def typeSafeSum[T <: Nat, W <: Nat, R <: Nat](x: T, y: W)
(implicit sum: Sum.Aux[T, W, R], error: R =:!= _7) = x
typeSafeSum(_3, _4)
but the error message is ambiguous implicit value, how can I customize the error message ?
Upvotes: 5
Views: 1864
Reputation: 32719
In this other question, I posted an answer that adress the issue of getting a better error message: https://stackoverflow.com/a/17047288/1632462
Here's the code:
@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
}
And now you get the error message error: Cannot prove that Foo =!= Foo
:
def doStuff[T <: Foo](x: T)(implicit ev: T =!= Foo) = x
doStuff(new Foo{}) // error: Cannot prove that Foo =!= Foo
doStuff(new Bar)// successful
Upvotes: 4
Reputation: 21557
Another way you can put constraint on your doStuff
method use magnet pattern, which is simply enhanced typeclass, e.g:
trait Foo
trait Bar
object DoStuff {
def doStuff[T](value: StuffMagnet[T]): Unit = value()
}
trait StuffMagnet[T] {
def apply(): Unit
}
object StuffMagnet {
implicit def forFoo(foo: Foo): StuffMagnet[Foo] =
new StuffMagnet[Foo] {
def apply(): Unit = ()
}
}
The difference in this case would be that you can't add implicitNotFound annotation for better compile time error message, you'll get a simple type mismatch, like:
Test.scala:20: error: type mismatch;
found : Test.Bar
required: Test.StuffMagnet[?]
DoStuff.doStuff(new Bar {})
^
one error found
Upvotes: 0
Reputation: 23046
A simple type class would be better than a type inequality test in this (and most other) instance(s).
Presumably the reason for wanting to exclude Foo
is that Bar
(and its siblings) have some properties which Foo
lacks. If that's the case then you should create a type class which captures those properties and make that a requirement on the type argument for doStuff
. You can use Scala's @implicitNotFound
annotation to make compiler error messages more comprehensible whenever that requirement isn't met.
@annotation.implicitNotFound(msg = "No Props instance for ${T}")
trait Props[T] {
def wibble(t: T): Double
}
trait Foo
// Note no Props instance for Foo ...
trait Bar extends Foo
object Bar {
// Props instance for Bar
implicit def barProps: Props[Bar] = new Props[Bar] {
def wibble(t: Bar): Double = 23.0
}
}
def doStuff[T <: Foo](t: T)(implicit props: Props[T]) = props.wibble(t)
scala> doStuff(new Foo {})
<console>:11: error: No Props instance for Foo
doStuff(new Foo {})
^
scala> doStuff(new Bar {})
res1: Double = 23.0
If there aren't any such properties which distinguish Foo
from Bar
then you should question your supposition that you need to exclude Foo
from doStuff
in the first place.
I'd be delighted if you use shapeless in your project, but you should use =!:=
(and Scala's own =:=
) only as a last resort, if at all.
Upvotes: 7