我喜欢 Miles Sabin 的第一个解决方案的简单性和有效性,但对我们得到的错误不是很有帮助这一事实有点不满意:
以如下定义为例:
def f[T]( implicit e: T =!= String ) {}
尝试执行f[String] 将无法编译:
<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]
^
我宁愿让编译器告诉我一些类似“T 与字符串没有区别”的内容
事实证明,如果添加另一个级别的隐式以使 ambiguity 错误发生,这很容易
隐式未找到错误。从那时起,我们可以使用implicitNotFound 注释来发出自定义错误消息:
@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
}
现在让我们尝试拨打f[String]:
scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
f[String]
^
这样更好。感谢编译器。
对于那些喜欢上下文绑定语法糖的人来说,最后一个技巧是,可以定义这个别名(基于类型 lambda):
type IsNot[A] = { type λ[B] = A =!= B }
那么我们可以这样定义f:
def f[T:IsNot[String]#λ] {}
是否更容易阅读是高度主观的。在任何情况下都比编写完整的隐式参数列表要短。
更新:为了完整起见,这里表示A 不是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 }
为了表达 A 不能转换为 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 }