1

我特别想定义 Semigroup 和 Sum 类型,它是一个 Semigroup,并使用 ScalaCheck 通常检查 Semigroup 的 Associative 属性。

我首先在 Haskell 中写了这个,因为我发现首先在 Haskell 语法中考虑这些东西然后将它们转换为 Scala 更容易。

所以在 Haskell 中,我写了以下在 GHCi 中有效的代码:

newtype Sum a = Sum a deriving (Show, Eq)

instance Num a => Num (Sum a) where
  (+) (Sum x) (Sum y) = Sum (x + y)

class Semigroup a where
  (<>) :: a -> a -> a

instance Num a => Semigroup (Sum a) where 
  (<>) = (+)

instance Arbitrary a => Arbitrary (Sum a) where
  arbitrary = fmap Sum arbitrary

semigroupAssocProp x y z = (x <> (y <> z)) == ((x <> y) <> z)
quickCheck (semigroupAssocProp :: Num a => Sum a -> Sum a -> Sum a -> Bool)

我正在尝试在 Scala 中创建大致等效的东西。到目前为止,我有你在下面看到的:

trait Semigroup[A] {
  def |+|(b: A): A
}

case class Sum[A: Numeric](n: A) extends Semigroup[Sum[A]] {
  def |+|(x: Sum[A]): Sum[A] = Sum[A](implicitly[Numeric[A]].plus(n, x.n)
}

val semigroupAssocProp = Prop.forAll { (x: Sum[Int], y: Sum[Int], z: Sum[Int]) =>
  (x |+| (y |+| z)) == ((x |+| y) |+| z)
} 

val chooseSum = for { n <- Gen.chooseNum(-10000, 10000) } yield Sum(n)
// => val chooseSum Gen[Sum[Int]] = org.scalacheck.Gen$$anon$<some hash>

我不知道如何Arbitrary为更通用Sum[Numeric]的 或至少 a创建一个实例,Gen[Sum[Numeric]]以及如何创建一个更通用的可以采用wheresemigroupAssocProp类型的 x、y 和 z并且是任何具体类型的实例。SS extends Semigroup[T]T

我真的想在功能上尽可能接近我在 Scala 中编写的 Haskell 版本。

4

1 回答 1

4

部分问题在于这是对您的 Haskell 代码的更直接翻译:

trait Semigroup[A] {
  def add(a: A, b: A): A
}

case class Sum[A](n: A)

object Sum {
  implicit def sumSemigroup[A: Numeric]: Semigroup[Sum[A]] =
    new Semigroup[Sum[A]] {
      def add(a: Sum[A], b: Sum[A]): Sum[A] =
        Sum(implicitly[Numeric[A]].plus(a.n, b.n))
    }
}

这不是字面翻译,因为我们不提供Numeric实例Sum[A](这会更痛苦,给定Numeric的接口),但它确实代表了 Scala 中类型类的标准编码。

现在您以与在 Haskell 中完全相同的方式提供一个Arbitrary实例:Sum[A]

import org.scalacheck.Arbitrary

implicit def arbitrarySum[A](implicit A: Arbitrary[A]): Arbitrary[Sum[A]] =
  Arbitrary(A.arbitrary.map(Sum(_)))

然后你可以定义你的属性:

import org.scalacheck.Prop

def semigroupAssocProp[A: Arbitrary: Semigroup]: Prop =
  Prop.forAll { (x: A, y: A, z: A) =>
    val semigroup = implicitly[Semigroup[A]]

    semigroup.add(x, semigroup.add(y, z)) == semigroup.add(semigroup.add(x, y), z)
  }

然后检查它:

scala> semigroupAssocProp[Sum[Int]].check
+ OK, passed 100 tests.

关键是 Scala 并没有像您的实现尝试那样使用子类型对类型类进行编码——相反,您将类型类定义为看起来与您class在 Haskell 中使用的方式非常相似的特征(或类)。例如,我Semigroup的 's|+|有两个参数,就像<>Haskell 中的一样Semigroupinstance但是,您可以通过实例化这些特征(或类)并将实例放入隐式范围来定义类型类实例,而不是使用单独的类似语言级别的机制。

于 2016-04-19T20:08:18.870 回答