Можно ли написать распределительный экземпляр для типа Gen ScalaCheck? - PullRequest
1 голос
/ 15 марта 2020

Я пытаюсь написать экземпляр Monad Transformer для типа Gen ScalaCheck.

То есть: такой тип, как следующий, который может использоваться в качестве монады, при условии, что нижележащий функтор F - это Монада.

case class GeneratorT[F[_], A](generator: Gen[F[A]])

object GeneratorT {
  implicit def monadForGeneratorT[F[_]: Monad]: Monad[GeneratorT[F, *]] = new Monad[GeneratorT[F, *]] {
        ...
    }
}

При написании этого я понял, что было бы полезно, если бы я смог написать экземпляр Distributive для Gen, потому что тогда я мог бы написать flatMap для GeneratorT следующим (несколько запутанным) образом:

    override def flatMap[A, B](ga: GeneratorT[F, A])(fun: A => GeneratorT[F, B]): GeneratorT[F, B] = {
      GeneratorT[F, B](ga.generator.flatMap(fa => fa.map(a => fun(a).generator).distribute(identity).map(_.flatten)))
    }

Инстинктивно я чувствую, что могу написать Distributive экземпляр для Gen, потому что Gen - это больше или less просто функция из некоторой конфигурации и начальное значение для значения, а функции являются дистрибутивными.

С учетом вышесказанного мне не удалось найти пример того, как кто-то делает это, и я изо всех сил пытаюсь написать это, потому что ScalaCheck не раскрывает внутренности Gen.

Возможно ли это?

1 Ответ

1 голос
/ 15 марта 2020

Мне кажется, я понимаю, почему это не совсем возможно; это связано с тем, что Gen поддерживает фильтрацию, и поэтому можно получить Gen, который не имеет допустимых значений.

Я думал о Gen как о функции (Properties, Seed) => A, но на самом деле это должно быть больше похоже на (Properties, Seed) => Option[A].

Первый тип распределяет, второй - нет.

Например, IO[Gen[A]] нельзя преобразовать в Gen[IO[A]], если Gen может дать сбой, потому что нет способ узнать о сбое без оценки IO.

Если вы притворяетесь, что любой генератор сгенерирует значение, если его оценивать достаточно много раз, и вы готовы столкнуться с исключениями, когда это не так, возможно реализовать Distributive следующим образом:

  implicit val distributiveForGen: Distributive[Gen] = new Distributive[Gen] {
    override def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => Gen[B]): Gen[G[B]] =
      Gen.parameterized(params => ga.map(a => f(a).pureApply(params, params.initialSeed.getOrElse(Seed(0)))))

    override def map[A, B](fa: Gen[A])(f: A => B): Gen[B] = fa.map(f)
  }
...