在Scala中,我可以在编译时强制执行类型相等.例如:
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.
Run Code Online (Sandbox Code Playgroud)
有没有办法强制执行类型A和类型B应该是不同的?
Mil*_*bin 47
我有一个更简单的解决方案,它也利用歧义,
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
Run Code Online (Sandbox Code Playgroud)
原始用例,
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"))
Run Code Online (Sandbox Code Playgroud)
更新
我们可以将这与我的"魔法类型系统技巧"(感谢@jpp ;-)联系如下,
type ¬[T] = T => Nothing
implicit def neg[T, U](t : T)(implicit ev : T =!= U) : ¬[U] = null
def notString[T <% ¬[String]](t : T) = t
Run Code Online (Sandbox Code Playgroud)
示例REPL会话,
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")
^
Run Code Online (Sandbox Code Playgroud)
Aar*_*rup 25
对Jean-Philippe的想法嗤之以鼻,这有效:
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)
Run Code Online (Sandbox Code Playgroud)
然后:
// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))
// doesn't compile
// Foo(1f, 1f)
// Foo("", "")
Run Code Online (Sandbox Code Playgroud)
我可能会将此简化如下,因为无论如何都可以避免对"作弊"的检查(例如Foo(1, 1)(null)
或=!=.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]
}
Run Code Online (Sandbox Code Playgroud)
Rég*_*les 15
我喜欢Miles Sabin的第一个解决方案的简单性和有效性,但对我们得到的错误不是很有帮助的事实有点不满意:
通过以下定义的示例:
def f[T]( implicit e: T =!= String ) {}
Run Code Online (Sandbox Code Playgroud)
Attemtping do 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]
^
Run Code Online (Sandbox Code Playgroud)
我宁愿让编译器告诉我"T与字符串没有区别"这一行的事情.事实证明,如果以这样一种方式添加另一级别的implicits,我们将模糊性错误转换为隐式错误就很容易了发现错误.从那时起,我们可以使用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
}
Run Code Online (Sandbox Code Playgroud)
现在让我们试着打电话f[String]
:
scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
f[String]
^
Run Code Online (Sandbox Code Playgroud)
那更好.谢谢编译器.
作为那些喜欢上下文绑定语法糖的人的最后一招,可以定义这个别名(基于类型lambdas):
type IsNot[A] = { type ?[B] = A =!= B }
Run Code Online (Sandbox Code Playgroud)
然后我们可以这样定义f
:
def f[T:IsNot[String]#?] {}
Run Code Online (Sandbox Code Playgroud)
是否更容易阅读是非常主观的.在任何情况下,肯定比写入完整的隐式参数列表短.
更新:为了完整性,这里表达的等效代码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 }
Run Code Online (Sandbox Code Playgroud)
并且表达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 }
Run Code Online (Sandbox Code Playgroud)
基于Landei的想法,以下似乎有效:
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)
Run Code Online (Sandbox Code Playgroud)
这是另一个尝试:
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)
Run Code Online (Sandbox Code Playgroud)
然后,再次:
// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))
// doesn't compile
// Foo(1f, 1f)
// Foo("", "")
Run Code Online (Sandbox Code Playgroud)
就像我的另一个提议一样,这里的目的是引入编译时的模糊性,A
并且B
是相同的.在这里,我们为A
相同的情况提供了两个含义B
,并且在不是这种情况时提供了明确的含义.
请注意,问题是您仍然可以通过手动调用=!=.notMeantToBeCalled1
或显式提供隐式参数=!=.unambigouslyDifferent
.我无法想到在编译时阻止这种情况的方法.但是,我们可以在运行时抛出异常,其中的技巧unambigouslyDifferent
需要一个证据参数本身来指示是否A
与它相同B
.但是等等......难道我们不是要证明正好相反吗?是的,这就是same
隐式参数的默认值为的原因null
.我们希望它null
适用于所有合法用途 - 唯一不会出现的情况null
是,当一个讨厌的用户打电话时Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float])
,我们可以通过抛出异常来防止这种欺骗行为.