2016-10-26 4 views
1

Я создал тип типа Monad, который очень похож на тип Play Json Reads[T], называемый ReadYamlValue.Как проверить функционально подобранную Monad с кошками + дисциплина

trait ReadYamlValue[T] { 
    def read(json: YamlValue): ReadResult[T] 
    // ... methods include map, flatMap, etc 
} 

Я создал кошку Monad экземпляр этого:

implicit val ReadYamlValueMonad: Monad[ReadYamlValue] = new Monad[ReadYamlValue] { 
    override def flatMap[A, B](fa: ReadYamlValue[A])(f: A => ReadYamlValue[B]): ReadYamlValue[B] = { 
    fa flatMap f 
    } 
    override def tailRecM[A, B](a: A)(f: A => ReadYamlValue[Either[A, B]]): ReadYamlValue[B] = { 
    ReadYamlValue.read[B] { yaml => 
     @tailrec def readB(reader: ReadYamlValue[Either[A, B]]): ReadResult[B] = { 
     reader.read(yaml) match { 
      case Good(Left(nextA)) => readB(f(nextA)) 
      case Good(Right(b)) => Good(b) 
      case Bad(error) => Bad(error) 
     } 
     } 
     readB(f(a)) 
    } 
    } 
    override def pure[A](x: A): ReadYamlValue[A] = ReadYamlValue.success(x) 
} 

А потом я хотел, чтобы проверить его с MonadLaws и ScalaCheck.

class CatsTests extends FreeSpec with discipline.MonadTests[ReadYamlValue] { 
    monad[Int, Int, Int].all.check() 
} 

Но я получаю:

could not find implicit value for parameter EqFA: cats.Eq[io.gloriousfuture.yaml.ReadYamlValue[Int]] 

Как определить Eq для того, что фактически является функцией? Сравнение равенства функции похоже на то, что я не хочу ... Является ли мой класс ReadYamlValue не монадой или даже функтором?

Один из способов сделать это, чтобы произвести произвольную выборку и сравнивать равенство результата:

implicit def eqReadYaml[T: Eq: FormatYamlValue: Arbitrary]: Eq[ReadYamlValue[T]] = { 
    Eq.instance { (a, b) => 
    val badYaml = arbitrary[YamlValue].getOrThrow 
    val goodValue = arbitrary[T].getOrThrow 
    val goodYaml = Yaml.write(goodValue) 
    Seq(badYaml, goodYaml).forall { yaml => 
     (a.read(yaml), b.read(yaml)) match { 
     case (Good(av), Good(bv)) => Eq.eqv(av, bv) 
     case (Bad(ae), Bad(be)) => Eq.eqv(ae, be) 
     case _ => false 
     } 
    } 
    } 
} 

Но это кажется, что это обходя определение равенства немного. Есть ли лучший или более канонический способ сделать это?

ответ

1

Похоже, используя произвольные экземпляры, как Цирцей делает это:

https://github.com/travisbrown/circe/blob/master/modules/testing/shared/src/main/scala/io/circe/testing/EqInstances.scala

Они принимают поток 16 образцов и сравнить результаты.

+0

Единственным недостатком этого, похоже, является то, что вряд ли можно создать единый пример, который оба будут «читать» успешно. Я боюсь, что все 16 будут результатом ошибок, и случай успеха не будет проверен. Это очень важно для тестирования «MonadLaws»? –

+0

Для стороны декодирования circe действительно должен генерировать некоторые произвольные значения «A», затем кодировать их, а затем следить за тем, чтобы декодеры делали то же самое. –