Я создал тип типа 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
}
}
}
}
Но это кажется, что это обходя определение равенства немного. Есть ли лучший или более канонический способ сделать это?
Единственным недостатком этого, похоже, является то, что вряд ли можно создать единый пример, который оба будут «читать» успешно. Я боюсь, что все 16 будут результатом ошибок, и случай успеха не будет проверен. Это очень важно для тестирования «MonadLaws»? –
Для стороны декодирования circe действительно должен генерировать некоторые произвольные значения «A», затем кодировать их, а затем следить за тем, чтобы декодеры делали то же самое. –