1

Ich versuche speziell Semigruppe und einen Sum-Typ zu definieren, der eine Semigruppe ist und die Associative-Eigenschaft der Semigroup generisch mit ScalaCheck prüft.Erstellen Sie eine beliebige Instanz für eine Fallklasse, die in ScalaCheck eine `Numeric` enthält?

Ich habe das zuerst in Haskell geschrieben, weil ich es leichter finde, zuerst an diese Dinge in der Haskell-Syntax zu denken und sie dann in Scala zu übersetzen.

So in Haskell, schrieb ich die folgenden, die in GHCi funktioniert:

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) 

Ich versuche, etwas entspricht in etwa in Scala zu erstellen. Bisher habe ich was sehen Sie unten:

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> 

Ich bin verloren, wie eine generische Sum[Numeric] eine Arbitrary Instanz zu erstellen, oder zumindest einen Gen[Sum[Numeric]] und wie ein allgemeineres semigroupAssocProp zu erstellen, die nehmen könnte ein x, y und z vom Typ S, wobei S extends Semigroup[T], wobei T irgendein Betontyp ist.

Ich versuche wirklich, so nah an der Haskell-Version zu kommen, die ich in Scala geschrieben habe.

Antwort

4

Teil des Problems ist, dass dies eine direktere Übersetzung Ihres Haskell-Code ist:

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)) 
    } 
} 

Es ist nicht eine wörtliche Übersetzung, da wir keine Numeric Instanz für Sum[A] (Versorgung, die mehr sein würde, von einem Schmerz, gegeben Numeric Schnittstelle, aber es stellt die Standardcodierung von Typklassen in Scala dar.

Sie nun eine Arbitrary Instanz für Sum[A] in genau der gleichen Weise wie in Haskell bieten:

import org.scalacheck.Arbitrary 

implicit def arbitrarySum[A](implicit A: Arbitrary[A]): Arbitrary[Sum[A]] = 
    Arbitrary(A.arbitrary.map(Sum(_))) 

Und dann können Sie Ihr Eigentum definieren:

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) 
    } 

Und dann überprüfen:

scala> semigroupAssocProp[Sum[Int]].check 
+ OK, passed 100 tests. 

Der entscheidende Punkt ist, dass Scala Typklasse nicht codiert Sie verwenden Subtyping auf die Art und Weise, wie Ihre Implementierung versucht. Stattdessen definieren Sie Ihre Typklassen als Merkmale (oder Klassen), die der Art, wie Sie in Haskell class verwenden, sehr ähnlich sind. Meine Semigroup 's |+|, zum Beispiel, nimmt zwei Argumente, genau wie die <> in der Haskell Semigroup. Anstatt einen separaten instance -ähnlichen Mechanismus auf Sprachenebene zu definieren, definieren Sie jedoch Ihre Klasseninstanzen, indem Sie diese Merkmale (oder Klassen) instanziieren und die Instanzen in einen impliziten Bereich einfügen.

+0

Wenn ich versuche, Ihren Code in der Scala REPL auszuführen, erhalte ich einen Fehler, der behauptet, dass der Sum-Typ keine Parameter akzeptiert. ': 18: Fehler: Sum.type nimmt keine Parameter def add (a: Summe [A], b: Summe [A]): ​​Summe [A] = Summe (implizit [numerisch [A]]. plus (an, bn)) ' – josiah

+2

@josiah Die Fallklasse und das Objekt 'Summe' sind ein Begleitpaar und müssen zusammen definiert werden, was nicht passiert, wenn Sie einfach in die REPL kopieren und einfügen, aber Sie können' : einfügen, um sie zusammen zu definieren. –

+0

Ihre Scala-Version sieht folgendermaßen aus: 'Instanz Num a => Semigroup (Summe a) where' mehr als die Version in der Frage. – pedrofurla

Verwandte Themen