为在 ScalaCheck 中包含“Numeric”的案例 class 创建一个任意实例?
Create an Arbitrary instance for a case class that holds a `Numeric` in ScalaCheck?
我专门尝试定义 Semigroup 和 'is a' Semigroup 的 Sum 类型,并通常使用 ScalaCheck 检查 Semigroup 的关联 属性。
我最初是在 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>
我不知道如何为更通用的 Sum[Numeric]
或至少 Gen[Sum[Numeric]]
创建 Arbitrary
实例以及如何创建更通用的 semigroupAssocProp
] 可以采用 S
类型的 x、y 和 z,其中 S extends Semigroup[T]
,其中 T
是任何具体类型。
我真的想在功能上尽可能接近我用 Scala 编写的 Haskell 版本。
部分问题在于这是对您的 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))
}
}
这不是字面翻译,因为我们没有为 Sum[A]
提供 Numeric
实例(考虑到 Numeric
的界面,这会更痛苦),但它确实代表了 Scala 中 classes 类型的标准编码。
现在您为 Sum[A]
提供一个 Arbitrary
实例,其方式与 Haskell:
中的完全相同
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 不会像您的实现尝试那样使用子类型对类型 classes 进行编码,而是将您的类型 classes 定义为特征(或 classes) 看起来与您在 Haskell 中使用 class
的方式非常相似。例如,我的 Semigroup
的 |+|
有两个参数,就像 Haskell Semigroup
中的 <>
一样。但是,您可以通过实例化这些特征(或 classes)并将实例放入隐式范围来定义类型 class 实例,而不是单独的 instance
类语言级机制。
我专门尝试定义 Semigroup 和 'is a' Semigroup 的 Sum 类型,并通常使用 ScalaCheck 检查 Semigroup 的关联 属性。
我最初是在 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>
我不知道如何为更通用的 Sum[Numeric]
或至少 Gen[Sum[Numeric]]
创建 Arbitrary
实例以及如何创建更通用的 semigroupAssocProp
] 可以采用 S
类型的 x、y 和 z,其中 S extends Semigroup[T]
,其中 T
是任何具体类型。
我真的想在功能上尽可能接近我用 Scala 编写的 Haskell 版本。
部分问题在于这是对您的 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))
}
}
这不是字面翻译,因为我们没有为 Sum[A]
提供 Numeric
实例(考虑到 Numeric
的界面,这会更痛苦),但它确实代表了 Scala 中 classes 类型的标准编码。
现在您为 Sum[A]
提供一个 Arbitrary
实例,其方式与 Haskell:
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 不会像您的实现尝试那样使用子类型对类型 classes 进行编码,而是将您的类型 classes 定义为特征(或 classes) 看起来与您在 Haskell 中使用 class
的方式非常相似。例如,我的 Semigroup
的 |+|
有两个参数,就像 Haskell Semigroup
中的 <>
一样。但是,您可以通过实例化这些特征(或 classes)并将实例放入隐式范围来定义类型 class 实例,而不是单独的 instance
类语言级机制。