强化types差异
在Scala中,我可以在编译时强制执行types相等。 例如:
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.
有没有办法强制执行typesA和typesB应该是不同的?
让·菲利普的想法嗤之以鼻,这是有效的:
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)
然后:
// compiles: Foo(1f, 1.0) Foo("", 1.0) Foo("", 1) Foo("Fish", Some("Fish")) // doesn't compile // Foo(1f, 1f) // Foo("", "")
我可能会简化如下,因为无论如何总是可以绕过“作弊”的检查(例如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] }
我有一个更简单的解决scheme,它也利用了模糊性,
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
原来的用例,
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"))
更新
我们可以把它和我的“神奇的types系统技巧” (谢谢@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
示例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") ^
我喜欢Miles Sabin的第一个解决scheme的简单性和有效性,但是对于我们得到的错误并不是非常有帮助的事实,有点不满意:
以下面的定义为例:
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与String不是不同的东西”的东西。事实certificate,如果以这样一种方式添加另一个层次的蕴涵是非常容易的,那么我们将模糊性误差转化为隐含的发现错误。 从那时起,我们可以使用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] ^
好多了 感谢编译器。
作为那些喜欢上下文绑定语法糖的人的最后一招,可以定义这个别名(基于lambdatypes):
type IsNot[A] = { type λ[B] = A =!= B }
那么我们可以这样定义f
:
def f[T:IsNot[String]#λ] {}
是否更容易阅读是非常主观的。 在任何情况下都比写完整的隐式参数列表要短。
更新 :为了完整性,这里expressionA
的等价代码不是B
的子types:
@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 }
基于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)
这是另一个尝试:
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)
然后,再次:
// compiles: Foo(1f, 1.0) Foo("", 1.0) Foo("", 1) Foo("Fish", Some("Fish")) // doesn't compile // Foo(1f, 1f) // Foo("", "")
和我的另一个build议一样,这里的目的是在A
和B
是相同的情况下引入编译时的模糊性。 在这里,我们为A
和B
相同的情况提供了两个含义,当不是这种情况时,含义是明确的。
请注意,问题在于您仍然可以通过手动调用=!=.notMeantToBeCalled1
或=!=.unambigouslyDifferent
来显式提供隐式参数=!=.unambigouslyDifferent
。 我想不出在编译时防止这种情况的方法。 但是,我们可以在运行时抛出一个exception,其中unambigouslyDifferent
需要一个证据参数来指示A
是否与B
相同。 但是等等…我们不是在试图certificate完全相反吗? 是的,这就是为什么same
隐式参数的默认值为null
。 我们期望它在所有合法用途中都是null
的 – 唯一不会为null
是当一个讨厌的用户调用例如Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float])
,可以通过抛出exception来防止这种欺骗行为。
那么这样的事情呢?
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) }
然后:
// compiles: Foo(1f, 1.0) Foo("", 1.0) Foo("", 1) Foo("Fish", Some("Fish")) // doesn't compile // Foo(1f, 1f) // Foo("", "")
当A
与B
,这个想法是解决模糊的,当它们不相同的时候是明确的。 为了进一步强调不应该调用不明确的方法,我添加了一个Nothing
types的隐式,它永远不会在周围(如果调用者试图显式地插入一个,那么肯定会看起来错误)。 ( DummyImplicit
的作用只是给前两个方法一个不同的签名。)
这不是一个答案,只是我能想到的答案的开始。 下面的代码将返回一个Yes
或No
取决于types是否相等,如果你问implicitly[AreEqual[A,B]]
。 如何从那里去实际做一个检查,我一直无法弄清楚。 也许整个方法是注定的,也许有人可以做出一些东西。 请注意, implicitly[No[A, B]]
将总是返回一些东西,不能使用它。 🙁
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) }
testing:
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)