强制类型差异

时间:2011-08-02 08:14:30

标签: scala types

在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.

有没有办法强制执行A​​类和B类应该不同?

7 个答案:

答案 0 :(得分:44)

我有一个更简单的解决方案,它也利用歧义,

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"))

<强>更新

我们可以将此与我的"magical typesystem tricks"(感谢@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")
                            ^

答案 1 :(得分:24)

对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)

然后:

// 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]
}

答案 2 :(得分:15)

我喜欢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与String不同”的内容 事实证明,如果以这样一种方式添加另一级别的隐含,我们就会变得非常容易,因为我们会转变歧义错误 进入隐式未找到错误。从那时起,我们可以使用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]
           ^

那更好。谢谢编译器。

作为那些喜欢上下文绑定语法糖的人的最后一招,可以定义这个别名(基于类型lambdas):

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 }

答案 3 :(得分:9)

根据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)

答案 4 :(得分:6)

这是另一次尝试:

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("", "")

与我的其他提案一样,此处的目的是在AB相同时引入编译时歧义。在这里,我们为AB相同的情况提供两个含义,并且在不是这种情况时隐含明确的含义。

请注意,问题是您仍然可以通过手动调用=!=.notMeantToBeCalled1=!=.unambigouslyDifferent来显式提供隐式参数。我无法想到在编译时阻止这种情况的方法。但是,我们可以在运行时抛出异常,其中unambigouslyDifferent需要一个证据参数,表明A是否与B相同。但是等等......难道我们不是要证明正好相反吗?是的,这就是same隐式参数的默认值为null的原因。对于所有合法用途我们都希望它是null - 唯一一次,当一个讨厌的用户呼叫例如nullFoo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float]),我们可以通过抛出异常来防止这种欺骗。

答案 5 :(得分:1)

那么这样的事情呢?

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("", "")

AB相同时,我们的想法是使解决方案不明确,而当它们不相同时,明确无误。为了进一步强调不应该调用模糊方法,我添加了一个类型为Nothing的隐式,它应该永远不会出现(如果调用者试图明确地插入一个调用者,那么它们肯定会出错)。 (DummyImplicit的作用只是为前两种方法提供不同的签名。)

答案 6 :(得分:1)

这不是一个答案,只是我能想到的答案的开头。如果您要求Yes,则以下代码将返回Noimplicitly[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)
}

测试:

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)