Создать произвольный экземпляр для класса case, который содержит `Numeric` в ScalaCheck?

Я специально пытаюсь определить Полугруппу и тип Суммы, который является Полугруппой, и проверять свойство Ассоциативной Полугруппы в общем, используя ScalaCheck.

Сначала я написал об этом в 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]или хотя бы Gen[Sum[Numeric]] и как создать более общий semigroupAssocProp это может принять х, у и г типа S где S extends Semigroup[T], с T быть любого конкретного типа.

Я действительно стараюсь максимально приблизиться по функциональности к версии на Haskell, которую я написал, насколько это возможно в Scala.

1 ответ

Решение

Отчасти проблема в том, что это более прямой перевод вашего кода на 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.

Теперь вы предоставляете Arbitrary экземпляр для Sum[A] точно так же, как в 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 не кодирует классы типов с использованием подтипов так, как пытается реализовать ваша реализация - вместо этого вы определяете классы типов как признаки (или классы), которые выглядят очень похоже на то, как вы используете class в Хаскеле. мой Semigroup"s |+|например, принимает два аргумента, так же как <> в Хаскеле Semigroup, Вместо отдельного instanceПодобно механизму на уровне языка, вы определяете экземпляры классов типов, создавая эти черты (или классы) и помещая экземпляры в неявную область видимости.

Другие вопросы по тегам