1

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.

josiah
  • 1,314
  • 1
  • 13
  • 33

1 Answers1

4

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.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • When I try to run your code in the Scala REPL, I get an error which claims the Sum type doesn't take parameters. `:18: error: Sum.type does not take parameters def add(a: Sum[A], b: Sum[A]): Sum[A] = Sum(implicitly[Numeric[A]].plus(a.n, b.n))` – josiah Apr 19 '16 at 20:27
  • 2
    @josiah The `Sum` case class and object are a companion pair and have to be defined together, which won't happen if you just copy and paste into the REPL, but you can use `:paste` to define them together. – Travis Brown Apr 19 '16 at 20:40
  • Your Scala version does look like `instance Num a => Semigroup (Sum a) where` more than the version in the question. – pedrofurla Apr 19 '16 at 20:58
  • It's there any way to preserve the infix operator style? The actual `sconcat` (<>) application now looks much more verbose and cumbersome. Especially now that I need to declare my type as implicitly a Semigroup or else call `.sumSemigroup' to use the Semigroup level operators. – josiah Apr 19 '16 at 21:05
  • 1
    @josiah Yeah, you can add syntax like that though enrichment classes (usually called something like `SemigroupOps`)—see e.g.: https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/syntax/semigroup.scala – Travis Brown Apr 19 '16 at 21:19
  • @pedrofurla Yeah, but there's no `instance Num a => Num (Sum a) where`. – Travis Brown Apr 19 '16 at 21:20
  • I had a feeling this would eventually boil down to reading the Cats source. At least I know what my evening will entail. – josiah Apr 19 '16 at 22:13
  • @josiah About your suggested edit: that's arguably not a type class at all—it's impossible to give e.g. `Int` a `Semigroup` instance under that encoding, which makes it more or less a non-starter. If you want to add another answer with that approach, that might make sense, but the approach I'm using isn't really the Cats or Scalaz way of doing things—it's just the Scala way. – Travis Brown Apr 20 '16 at 18:53
  • @TravisBrown I actually meant to add this edit to my OP, but hit the wrong edit button. I agree, this should go under as another answer or else as an edit to the OP. – josiah Apr 20 '16 at 18:56
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109720/discussion-between-josiah-and-travis-brown). – josiah Apr 20 '16 at 18:57