问题
I'm specifically trying to define Semigroup and a Sum type which 'is a' Semigroup and check the Associative property of Semigroup generically using ScalaCheck.
I first wrote this out in Haskell because I find it easier to think of these things first in Haskell syntax and then translate them to Scala.
So in Haskell, I wrote the following which works in 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)
I'm trying to create something roughly equivalent in Scala. So far, I have what you see below:
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>
I'm lost on how to create an Arbitrary
instance for a more generic Sum[Numeric]
, or at least a Gen[Sum[Numeric]]
and how to create a more generic semigroupAssocProp
that could take an x, y, and z of type S
where S extends Semigroup[T]
, with T
being any concrete type.
I'm really trying to get as close in functionality to the Haskell version I wrote as possible in Scala.
回答1:
Part of the issue is that this is a more direct translation of your Haskell code:
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))
}
}
It's not a literal translation, since we don't supply a Numeric
instance for Sum[A]
(which would be more of a pain, given Numeric
's interface), but it does represent the standard encoding of type classes in Scala.
Now you provide an Arbitrary
instance for Sum[A]
in exactly the same way as in Haskell:
import org.scalacheck.Arbitrary
implicit def arbitrarySum[A](implicit A: Arbitrary[A]): Arbitrary[Sum[A]] =
Arbitrary(A.arbitrary.map(Sum(_)))
And then you can define your property:
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)
}
And then check it:
scala> semigroupAssocProp[Sum[Int]].check
+ OK, passed 100 tests.
The key point is that Scala doesn't encode type classes using subtyping in the way that your implementation tries to do—instead you define your type classes as traits (or classes) that look very similar to the way you use class
in Haskell. My Semigroup
's |+|
, for example, takes two arguments, just like the <>
in the Haskell Semigroup
. Instead of a separate instance
-like language-level mechanism, though, you define your type class instances by instantiating these traits (or classes) and putting the instances into implicit scope.
来源:https://stackoverflow.com/questions/36724879/create-an-arbitrary-instance-for-a-case-class-that-holds-a-numeric-in-scalache