簡體   English   中英

為在ScalaCheck中包含`Numeric`的案例類創建一個任意實例?

[英]Create an Arbitrary instance for a case class that holds a `Numeric` in ScalaCheck?

我專門嘗試定義Semigroup和一個“是” Semigroup的Sum類型,並通常使用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>

我不知道如何為更通用的Sum[Numeric]或至少一個Gen[Sum[Numeric]]創建Arbitrary實例,以及如何創建可以采用x,y和z的更通用的semigroupAssocProp類型S ,其中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的接口,這會更Numeric ),但是它確實代表了Scala中類型類的標准編碼。

現在,以與Haskell中完全相同的方式為Sum[A]提供一個Arbitrary實例:

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不會按照實現嘗試的方式使用子類型對類型類進行編碼,而是將類型類定義為特征(或類),這些特征與在Haskell中使用class的方式非常相似。 我的Semigroup|+| 例如,接受兩個參數,就像Haskell Semigroup<>一樣。 但是,您可以通過實例化這些特征(或類)並將實例置於隱式作用域中,來定義類型類實例,而不是使用類似instance的語言級機制。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM