Scala 实现职能的价值平等

Scala 实现职能的价值平等,scala,equals,Scala,Equals,如何重写equals,以在特定情况下检查函数的值等价性?例如,假设我们有以下f和g函数 val f = (x: Int) => "worf" + x val g = (x: Int) => "worf" + x 我们如何使断言(f==g)通过 我试着扩展Function1,并通过生成器实现了相等,就像这样 trait Function1Equals extends (Int => String) { override def equals(obj: Any): Boole

如何重写
equals
,以在特定情况下检查函数的值等价性?例如,假设我们有以下
f
g
函数

val f = (x: Int) => "worf" + x
val g = (x: Int) => "worf" + x
我们如何使
断言(f==g)
通过

我试着扩展
Function1
,并通过生成器实现了相等,就像这样

trait Function1Equals extends (Int => String) {
  override def equals(obj: Any): Boolean = {
    val b = obj.asInstanceOf[Function1Equals]
    (1 to 100).forall { _ =>
      val input = scala.util.Random.nextInt
      apply(input) == b(input)
    }
  }
}

implicit def functionEquality(f: Int => String): Function1Equals = (x: Int) => f(x)
但无法获得隐式转换以使用
=
,可能是由于。Scalactics的接近了

import org.scalactic.TripleEquals._
import org.scalactic.Equality

implicit val functionEquality = new Equality[Int => String] {
  override def areEqual(a: Int => String, b: Any): Boolean =
    b match {
      case p: (Int => String) =>

        (1 to 100).forall { _ =>
          val input = scala.util.Random.nextInt
          a(input) == p(input)
        }

      case _ => false
    }
}

val f = (x: Int) => "worf" + x
val g = (x: Int) => "worf" + x
val h = (x: Int) => "picard" + x


assert(f === g) // pass
assert(f === h) // fail

首先,函数相等不是一个简单的主题(扰流板:它无法正确实现;请参见示例和相应的答案),但是让我们假设您的方法“为一百个随机输入断言相同的输出”这已经足够好了

重写
=
的问题是它已经为
函数1
实例实现了。所以你有两个选择:

  • 定义自定义特征(您的方法)并使用
    =
  • 使用operation
    isEqual
    定义一个typeclass,并为
    Function1
两种选择都有权衡

在第一种情况下,您不必使用标准的Scala
Function1
trait,而是必须将每个函数包装到自定义trait中。您这样做了,但随后尝试实现隐式转换,该转换将为您“幕后”完成从标准
Function1
Function1Equals
的转换。但正如你自己意识到的那样,这是行不通的。为什么?因为对于
Function1
实例已经存在一个方法
=
,所以编译器没有理由启动隐式转换。必须将每个
Function1
实例包装到自定义包装器中,以便调用重写的
=

下面是示例代码:

trait MyFunction extends Function1[Int, String] {
  override def apply(a: Int): String
  override def equals(obj: Any) = {
    val b = obj.asInstanceOf[MyFunction]
    (1 to 100).forall { _ =>
      val input = scala.util.Random.nextInt
      apply(input) == b(input)
    }
  }
}

val f = new MyFunction {
  override def apply(x: Int) = "worf" + x 
}
val g = new MyFunction {
  override def apply(x: Int) = "worf" + x
}
val h = new MyFunction {
  override def apply(x: Int) = "picard" + x
}

assert(f == g) // pass
assert(f == h) // fail
第二种选择是继续使用标准
Function1
实例,但使用自定义方法进行相等比较。这可以通过类型类方法轻松实现:

  • 定义一个通用特征
    MyEquals[a]
    ,它将具有所需的方法(我们称之为
    isEqual
  • Function1[Int,String]
  • 定义一个helper隐式类,只要存在
    MyEquals[a]
    的隐式实现,该类将为
    a
    类型的某些值提供方法
    isEqual
    (并且我们在上一步中确保了
    MyEquals[Function1[Int,String]]
然后代码如下所示:

trait MyEquals[A] {
  def isEqual(a1: A, a2: A): Boolean
}

implicit val function1EqualsIntString = new MyEquals[Int => String] {
  def isEqual(f1: Int => String, f2: Int => String) =
    (1 to 100).forall { _ =>
      val input = scala.util.Random.nextInt
      f1(input) == f2(input)
   }
}

implicit class MyEqualsOps[A: MyEquals](a1: A) {
  def isEqual(a2: A) = implicitly[MyEquals[A]].isEqual(a1, a2)
}

val f = (x: Int) => "worf" + x
val g = (x: Int) => "worf" + x
val h = (x: Int) => "picard" + x

assert(f isEqual g) // pass
assert(f isEqual h) // fail
但正如我所说,保持第一种方法(使用
==
)和第二种方法(使用标准
Function1
trait)的优势是不可能的。然而,我认为使用
=
甚至不是一种优势。请继续阅读,找出原因

这很好地说明了为什么类型类比继承更有用、更强大。与其从某个超类对象继承
==
并重写它,这对于我们无法修改的类型(如
Function1
)是有问题的,不如使用一个typeclass(我们称之为
Equal
),它为许多类型提供了相等方法

因此,如果范围中不存在
Equal[Function1]
的隐式实例,我们只需提供自己的实例(就像我在第二段代码中所做的那样),编译器就会使用它。另一方面,如果
Equal[Function1]
的隐式实例确实存在于某个地方(例如,在标准库中),它对我们没有任何改变-我们仍然只需要提供自己的实例,它将“覆盖”现有实例

现在最好的部分是:这样的类型类已经存在于和中。它分别被称为
Equal
Eq
,它们都将它们的相等比较方法命名为
=
。这就是为什么我早些时候说过,我甚至不会考虑使用<代码>=< /代码>作为一个优势。谁还需要
==
在代码库中始终使用scalaz或CAT意味着您将依赖
===
而不是
==
无处不在,您的生活将变得简单(r)

但不要指望函数相等;整个要求都很奇怪,而且不好。为了提供一些见解,我回答你的问题时假装这很好,但最好的答案是——根本不要依赖函数相等。

只是指出,这解释了为什么你的问题是不可判定的@斯劳克的相关问题解释了其余问题。