我需要一些帮助来进行有关f#中生成器的练习。
功能
List.zip : ('a list -> 'b list -> ('a * 'b) list)
和
List.unzip : (('a * 'b) list -> 'a list * 'b list)
在对列表进行运算的条件下彼此相反 相同的长度。 为一对长度相同的整数列表制定一个任意数组
我试图写一些代码:
let length xs ys =
List.length xs = List.length ys
let samelength =
Arb.filter length Arb.from<int list>
它不起作用,我在相同长度的长度中遇到类型不匹配:
错误:类型不匹配。期望为
'a list -> bool
但给定为'a list -> 'b list -> bool
。 bool类型与'a list -> bool
类型不匹配。
编辑:
根据建议,我尝试遵循步骤概述,但被困住了。
let sizegen =
Arb.filter (fun x -> x > 0) Arb.from<int>
let listgen =
let size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
xs, ys
当然还有错误类型不匹配:
错误:键入mistmatch。预期为
类型int
类型,但此处为Arbitrary<int>
修改
我解决了该练习,但是当我进行测试时,我的生成器似乎无法正常工作,看来又调用了另一个生成器。
let samelength (xs, ys) =
List.length xs = List.length ys
let arbMyGen2 = Arb.filter samelength Arb.from<int list * int list>
type MyGeneratorZ =
static member arbMyGen2() =
{
new Arbitrary<int list * int list>() with
override x.Generator = arbMyGen2 |> Arb.toGen
override x.Shrinker t = Seq.empty
}
let _ = Arb.register<MyGeneratorZ>()
let pro_zip (xs: int list, ys: int list) =
(xs, ys) = List.unzip(List.zip xs ys)
do Check.Quick pro_zip
我得到了错误:
错误:System.ArgumentException:list1比list2短1个元素
但是为什么呢?我的生成器只应生成两个长度相同的列表。
答案 0 :(得分:4)
如果我们查看API reference for the Arb
module,并将鼠标悬停在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仅生成一个整数列表,它将与它的长度进行比较吗?没有第二个列表可以比较,因此编译器实际上无法将您的代码转换为有意义的代码。
您可能想做的(尽管有一个问题,我将在稍后讨论)是生成一个 pair 列表,然后将其传递给您的{{1}谓词。也就是说,您可能想要length
。这将生成一对彼此完全独立的整数列表。然后,您在Arb.from<int list * int list>
函数中仍然会遇到类型不匹配的情况,但是您只需要将其签名从length
转换为let length xs ys =
,例如让它接收一个包含一对列表的单个参数,而不是将每个列表作为单独的参数。经过这些调整后,您的代码如下所示:
let length (xs,ys) =
但是这仍然存在问题。具体来说,如果我们查看FsCheck documentation,就会发现以下警告:
使用
let length (xs,ys) = List.length xs = List.length ys let samelength = Arb.filter length Arb.from<int list * int list>
时,请确保为谓词提供很高的返回Gen.filter
的机会。如果谓词丢弃“太多”的候选项,则可能导致测试运行速度变慢或根本无法终止。
顺便说一下,这与true
一样适用于Arb.filter
。您的代码当前的状态,这是一个问题,因为您的过滤器将丢弃大多数列表对。由于列表是彼此独立生成的,因此大多数情况下它们的长度是不同的,因此您的过滤器通常会返回Gen.filter
。我建议使用另一种方法。既然您已经说过这是一个练习,所以我不会为您编写代码,因为您可以自己动手学习更多;我只给您概述您要采取的步骤。
false
,该整数将为该对中两个列表的大小。 (要获得奖励积分,请使用n
来获取您应生成的数据的“当前大小”,并将Gen.sized
生成为介于0和n
之间的值,以便您的列表对生成器,例如FsCheck的默认列表生成器,将创建从小开始并逐渐变大的列表。size
生成两个列表。 (您甚至可以执行Gen.listOfLength n
来轻松生成一对大小相同的列表)。Gen.two (Gen.listOfLength n)
,而没有收缩器的Arbitrary
并不是非常好在实践中很有用。您可能可以在此处使用Arbitrary
进行某些操作,其中映射器为Arb.mapFilter
,因为您已经在生成匹配长度的列表,但是过滤器是您的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>
参数。有多种方法可以执行此操作,但最简单的方法是FsCheck为我们提供的int
计算表达式。
顺便说一句,如果您不知道什么是计算表达式,它们就是F#最强大的功能:它们在幕后非常复杂,但是它们使您可以编写看起来很简单的代码。您应该为https://fsharpforfunandprofit.com/series/computation-expressions.html和https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html添加书签,并计划以后再阅读。如果您在第一次,第二次甚至第五次阅读时都不理解它们,请不要担心:这很好。只要继续回到这两篇文章,并在实践中使用诸如gen { ... }
或gen
之类的计算表达式,最终这些概念就会变得清晰起来。每次阅读这些系列文章时,您都会学到更多,而当这一切在大脑中“点击”时,您就会接近启蒙的那一刻。
但是回到您的代码。就像我说的那样,您想使用seq
计算表达式。在gen { ... }
表达式内,gen { ... }
赋值会将let!
对象“解包”到生成的Gen<Foo>
中,然后可以在其他代码中使用它。您想对Foo
int进行哪些操作。因此,我们将在您的代码中包装一个size
表达式,并获得以下信息:
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>
还是{{ 1}}。