Если мы посмотрим на ссылку API для модуля Arb
и наведем курсор мыши на определение filter
, вы увидите, что тип Arb.filter
:
pred:('a -> bool) -> a:Arbitrary<'a> -> a:Arbitrary<'a>
Это означает, что предикат должен быть функцией одного параметра, который возвращает bool
.Но ваша length
функция является функцией двух параметров.Вы хотите превратить его в функцию только одного параметра.
Подумайте об этом так.Когда вы пишете Arb.filter length Arb.from<int list>
, вы говорите: «Я хочу сгенерировать произвольный int list
(только по одному за раз) и отфильтровать его в соответствии с правилом length
».Но написанное вами правило length
берет два списка и сравнивает их длину.Если FsCheck генерирует только один список целых чисел, с чем он будет сравнивать его длину?Нет второго списка для сравнения, поэтому компилятор не может на самом деле превратить ваш код во что-то, что имеет смысл.
То, что вы, вероятно, хотели сделать (хотя есть проблема с этим, о которой я расскажучерез минуту) генерируется пара списков, а затем передается в ваш предикат length
.Тоесть ты наверное хотел Arb.from<int list * int list>
.Это сгенерирует пару целочисленных списков, полностью независимых друг от друга.Тогда вы все равно получите несоответствие типов в вашей функции length
, но вам просто нужно повернуть ее сигнатуру с let length xs ys =
на let length (xs,ys) =
, например, получить единственный аргумент , содержащий парусписков, вместо того, чтобы каждый список в качестве отдельного аргумента.После этих настроек ваш код выглядит следующим образом:
let length (xs,ys) =
List.length xs = List.length ys
let samelength =
Arb.filter length Arb.from<int list * int list>
Но с этим все еще остаются проблемы.В частности, если мы посмотрим на документацию FsCheck , мы обнаружим следующее предупреждение:
При использовании Gen.filter
обязательно предоставьте предикат с высокой вероятностью возврата true
.Если предикат отбрасывает «слишком много» кандидатов, это может привести к тому, что тесты будут выполняться медленнее или вообще не завершаться.
Кстати, это относится к Arb.filter
так же, как и к Gen.filter
.То, как ваш код в настоящее время стоит, это проблема, потому что ваш фильтр отбрасывает большинство пар списков.Поскольку списки создаются независимо друг от друга, чаще всего они имеют разную длину, поэтому ваш фильтр будет возвращать false
большую часть времени.Я бы предложил другой подход.Поскольку вы сказали, что это упражнение, я не буду писать код для вас, поскольку вы узнаете больше, сделав это самостоятельно;Я просто дам вам схему шагов, которые вы хотите предпринять.
- Сгенерируйте неотрицательный int
n
, который будет размером обоих списков в паре.(Для получения бонусных баллов используйте Gen.sized
, чтобы получить «текущий размер» данных, которые вы должны сгенерировать, и сгенерируйте n
в качестве значения от 0 до size
, чтобы ваш генератор пар списков, как и список по умолчанию FsCheckгенератор, создаст списки, которые начинаются с малого и постепенно увеличиваются). - Используйте
Gen.listOfLength n
для создания обоих списков.(Вы могли бы даже сделать Gen.two (Gen.listOfLength n)
, чтобы легко сгенерировать пару списков одинакового размера). - Не забудьте написать подходящее сокращение для пары списков, потому что упражнение хочет, чтобы вы сгенерировалиправильные
Arbitrary
и Arbitrary
, которые не имеют термоусадочную пленку, не очень полезны на практике.Вы, вероятно, можете что-то сделать с Arb.mapFilter
здесь, где mapper равен id
, потому что вы уже генерируете списки соответствующей длины, но фильтр является вашим предикатом length
.Затем используйте Arb.fromGenShrink
, чтобы превратить ваши генераторные и усадочные функции в правильный экземпляр Arbitrary
.
Если вам не хватит этой схемы, задайте еще один вопрос о том, где вы находитесь.Я застрял, и я буду рад помочь, как только смогу.
Редактировать:
В вашем редактировании вы пытаетесь написать генератор списков, используя sizegen
, у вас есть следующий код, который не работает:
let listgen =
let size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
xs, ys
Здесь sizegen
- это Gen<int>
, и вы хотите извлечь из него параметр int
.Есть несколько способов сделать это, но самым простым является вычислительное выражение gen { ... }
, которое нам предоставил FsCheck.
Кстати, если вы не знаете, что такое выражения вычислений, они являются одними из самых мощных функций F #: они очень сложны, но позволяют вам писать очень простой на вид код.Вы должны добавить в закладки https://fsharpforfunandprofit.com/series/computation-expressions.html и https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html и планировать прочитать их позже.Не беспокойтесь, если вы не понимаете их во время первого, второго или даже пятого чтения: это нормально.Просто продолжайте возвращаться к этим двум сериям статей и использовать на практике выражения вычислений, такие как gen
или seq
, и в конце концов концепции станут ясными.И каждый раз, когда вы читаете эти серии, вы узнаете больше и приближаетесь к тому моменту просветления, когда все это «щелкает» в вашем мозгу.
Но вернемся к вашему коду.Как я уже сказал, вы хотите использовать вычислительное выражение gen { ... }
.Внутри выражения gen { ... }
назначение let!
"развернет" объект Gen<Foo>
в сгенерированный Foo
, который затем можно будет использовать в дальнейшем коде.Что вы хотите сделать со своим size
int.Поэтому мы просто обернем выражение gen { ... }
вокруг вашего кода и получим следующее:
let listgen =
gen {
let! size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
return (xs, ys)
}
Обратите внимание, что я также добавил ключевое слово return
в последнюю строку.Внутри вычислительного выражения return
имеет противоположный эффект let!
.Ключевое слово let!
разворачивает значение (тип изменяется от Gen<Foo>
до Foo
), а ключевое слово return
переносит значение (тип идет от Foo
до Gen<Foo>
).Таким образом, строка return
принимает int list * int list
и превращает ее в Gen<int list * int list>
.Есть некоторый очень сложный код, происходящий изнутри, но на поверхностном уровне выражения вычисления вам просто нужно подумать с точки зрения типов «разворачивания» и «обертывания», чтобы решить, использовать ли let!
или return
.