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.
Part of the issue is that this is a more direct translation of your Haskell code:
It's not a literal translation, since we don't supply a
Numeric
instance forSum[A]
(which would be more of a pain, givenNumeric
's interface), but it does represent the standard encoding of type classes in Scala.Now you provide an
Arbitrary
instance forSum[A]
in exactly the same way as in Haskell:And then you can define your property:
And then check it:
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. MySemigroup
's|+|
, for example, takes two arguments, just like the<>
in the HaskellSemigroup
. Instead of a separateinstance
-like language-level mechanism, though, you define your type class instances by instantiating these traits (or classes) and putting the instances into implicit scope.