Ответ 1
Не зная точно с Hilbert epsilon, я бы взял более фундаментальный подход и использовал ScalaCheck Arbitrary
и Gen
, чтобы выбрать функции для использования.
Сначала определите базовый класс для функций, которые вы собираетесь генерировать. В общем случае возможно генерировать функции, имеющие undefined результаты (например, деление на ноль), поэтому мы будем использовать PartialFunction
как наш базовый класс.
trait Fn[A, B] extends PartialFunction[A, B] {
def isDefinedAt(a: A) = true
}
Теперь вы можете предоставить некоторые реализации. Переопределить toString
, поэтому сообщения об ошибках ScalaCheck понятны.
object Identity extends Fn[Int, Int] {
def apply(a: Int) = a
override def toString = "a"
}
object Square extends Fn[Int, Int] {
def apply(a: Int) = a * a
override def toString = "a * a"
}
// etc.
Я решил генерировать унарные функции из двоичных функций, используя классы case, передавая дополнительные аргументы конструктору. Не единственный способ сделать это, но я считаю это самым простым.
case class Summation(b: Int) extends Fn[Int, Int] {
def apply(a: Int) = a + b
override def toString = "a + %d".format(b)
}
case class Quotient(b: Int) extends Fn[Int, Int] {
def apply(a: Int) = a / b
override def isDefinedAt(a: Int) = b != 0
override def toString = "a / %d".format(b)
}
// etc.
Теперь вам нужно создать генератор Fn[Int, Int]
и определить его как неявный Arbitrary[Fn[Int, Int]]
. Вы можете продолжать добавлять генераторы, пока не будете синими в лице (полиномы, составляющие сложные функции из простых и т.д.).
val funcs = for {
b <- arbitrary[Int]
factory <- Gen.oneOf[Int => Fn[Int, Int]](
Summation(_), Difference(_), Product(_), Sum(_), Quotient(_),
InvDifference(_), InvQuotient(_), (_: Int) => Square, (_: Int) => Identity)
} yield factory(b)
implicit def arbFunc: Arbitrary[Fn[Int, Int]] = Arbitrary(funcs)
Теперь вы можете определить свои свойства. Используйте intG.isDefinedAt(a)
, чтобы избежать результатов undefined.
property("left identity simple funcs") = forAll { (a: Int, intG: Fn[Int, Int]) =>
intG.isDefinedAt(a) ==> (fCat.compose(fCat.id[Int])(intG)(a) == intG(a))
}
property("right identity simple funcs") = forAll { (a: Int, intG: Fn[Int, Int]) =>
intG.isDefinedAt(a) ==> (fCat.compose(intG)(fCat.id)(a) == intG(a))
}
В то время как то, что я показал, только обобщает тестируемую функцию, надеюсь, это даст вам представление о том, как использовать обход расширенного типа для обобщения по типу.