6

I am fairly new to ScalaCheck (and Scala entirely) so this may be a fairly simple solution

I am using ScalaCheck to generate tests for an AST and verifying that the writer/parser work. I have these files

AST.scala

package com.test

object Operator extends Enumeration {
  val Add, Subtract, Multiply, Divide = Value
}

sealed trait AST
case class Operation(left: AST, op: Operator.Value, right: AST) extends AST
case class Literal(value: Int) extends AST

GenOperation.scala

import com.test.{AST, Literal}

import org.scalacheck._
import Shrink._
import Prop._
import Arbitrary.arbitrary    

object GenLiteral extends Properties("AST::Literal") {
  property("Verify parse/write") = forAll(genLiteral){ (node) =>
    //  val string_version = node.writeToString() // AST -> String
    //  val result = Parse(string_version) // String -> AST
    true
  }

  def genLiteral: Gen[Literal] = for {
    value <- arbitrary[Int]
  } yield Literal(value)

  implicit def shrinkLiteral: Shrink[AST] = Shrink {
    case Literal(value) =>
      for {
        reduced <- shrink(value)
      } yield Literal(reduced)
  }
}

GenOperation.scala

import com.test.{AST, Operation}

import org.scalacheck._
import Gen._
import Shrink._
import Prop._

import GenLiteral._

object GenOperation extends Properties("AST::Operation") {
  property("Verify parse/write") = forAll(genOperation){ (node) =>
    //  val string_version = node.writeToString() // AST -> String
    //  val result = Parse(string_version) // String -> AST
    true
  }

  def genOperation: Gen[Operation] = for {
    left <- oneOf(genOperation, genLiteral)
    right <- oneOf(genOperation, genLiteral)
    op <- oneOf(Operator.values.toSeq)
  } yield Operation(left,op,right)

  implicit def shrinkOperation: Shrink[AST] = Shrink {
    case Operation(l,o,r) =>
      (
        for {
          ls <- shrink(l)
          rs <- shrink(r)
        } yield Operation(ls, o, rs)
        ) append (
        for {
          ls <- shrink(l)
        } yield Operation(ls, o, r)
        ) append (
        for {
          rs <- shrink(r)
        } yield Operation(l, o, rs)
        ) append shrink(l) append shrink(r)
  }

}

In the example code I wrote (what is pasted above) I get the error

ambiguous implicit values:
 both method shrinkLiteral in object GenLiteral of type => org.scalacheck.Shrink[com.test.AST]
 and method shrinkOperation in object GenOperation of type => org.scalacheck.Shrink[com.test.AST]
 match expected type org.scalacheck.Shrink[com.test.AST]
          ls <- shrink(l)

How do I write the shrink methods for this?

Chase Walden
  • 1,152
  • 1
  • 11
  • 30

1 Answers1

4

You have two implicit instances of Shrink[AST] and so the compiler complains about ambiguous implicit values.

You could re-write your code as:

implicit def shrinkLiteral: Shrink[Literal] = Shrink {
  case Literal(value) => shrink(value).map(Literal)
}

implicit def shrinkOperation: Shrink[Operation] = Shrink {
  case Operation(l,o,r) =>
    shrink(l).map(Operation(_, o, r)) append
    shrink(r).map(Operation(l, o, _)) append ???
}

implicit def shrinkAST: Shrink[AST] = Shrink {
  case o: Operation => shrink(o)
  case l: Literal => shrink(l)
}
Federico Pellegatta
  • 3,777
  • 1
  • 14
  • 28
  • This is exactly what I am looking for. I guess as a side question, since I have three different files, one for AST, Operation, and Literal respectively, how do I get Scala to recognize the implicit def's? – Chase Walden Apr 04 '17 at 16:29
  • You could split `Literal` and `Operation` implicits (both `Gen` and `Shrink`) into two distinct traits. Then you can mix them into a single `AST` trait and define a `Shrink[AST]` inside it. Mix those traits in `Properties` objects when needed. – Federico Pellegatta Apr 04 '17 at 19:30
  • An alternative solution could be not to rely on implicits and define `shrinkAST` explicitly using `shrinkLiteral(l)` and `shrinkOperation(o)`. – Federico Pellegatta Apr 04 '17 at 19:37
  • Thank you! This helps a lot! – Chase Walden Apr 04 '17 at 20:06