【问题标题】:How to accumulate a list of FsCheck generators into a single value?如何将 FsCheck 生成器列表累积为单个值?
【发布时间】:2018-04-15 11:20:42
【问题描述】:

我编写了一个 FsCheck 生成器,它生成随机 glob 语法模式(例如 a*c?)以及与该模式匹配的随机字符串(例如 abcd)。但是我的解决方案使用了一个可变变量,我对此感到很惭愧。看看:

open FsCheck

type TestData = {Pattern: string; Text: string}

let stringFrom alphabet =
    alphabet |> Gen.elements |> Gen.listOf |> Gen.map (List.map string >> List.fold (+) "")

let singleCharStringFrom alphabet =
    alphabet |> Gen.elements |> Gen.map string

let matchingTextAndPatternCombo = gen {    
    let toGen = function
        | '*' -> stringFrom ['a'..'f']
        | '?' -> singleCharStringFrom ['a'..'f']
        | c -> c |> string |> Gen.constant

    let! pattern = stringFrom (['a'..'c']@['?'; '*'])
    let mutable text = ""

    for gen in Seq.map toGen pattern do
        let! textPart = gen
        text <- text + textPart

    return {Pattern = pattern; Text = text}
}

注意text 是可变的,它的值是如何在循环中累积的。

我的直觉告诉我,一定有办法将生成器 fold 转换为 text,但我不知道怎么做,因为我不明白 let! 是如何在幕后工作的(目前)。我正在考虑类似于以下内容:

let! text = pattern |> Seq.map toGen |> Seq.fold (?) (Gen.constant "")

我在正确的轨道上吗? fold 的累加器和种子应该是什么样的?

【问题讨论】:

    标签: f# fscheck property-based-testing


    【解决方案1】:

    您认为可以在这里使用 fold 之类的东西的直觉是正确的,但问题是您需要一个 fold 版本,其中折叠函数返回 Gen&lt;'T&gt; 计算 - 很正常来自 F# 的 List.fold不管用。在这种情况下,我认为使用突变非常好 - 你的代码对我来说看起来很清楚。

    查看Gen 模块中的函数,我没有看到fold 的版本,但我认为Gen.sequence 可以很好地满足您的需求:

    let! textParts = Gen.sequence (Seq.map toGen pattern)
    let text = String.concat "" textParts
    

    Gen.sequence 函数接受一个生成器列表并返回一个生成器,该生成器使用这些生成器生成一个值列表 - 这样,您可以一次生成所有文本部分,然后将结果连接起来。

    如果您想编写自己的 fold 并使用它,它看起来会很不错 像这样:

    let rec fold f init xs = gen {
      match xs with
      | [] -> return init 
      | x::xs -> 
          let! state = f init x
          return! fold f state xs }
    

    折叠生成器的代码是:

    let! text = 
      Seq.map toGen pattern |> List.ofSeq |> fold (fun (text:string) g -> gen {
        let! textPart = g
        return text + textPart }) ""
    

    我没有对此进行测试,因此可能存在错误(很可能,它折叠的方式错误,因此您最终会得到反转的字符串),但总体结构应该是正确的。

    【讨论】:

    • 同时我想出了以下累加器:fun currGen nextGen -&gt; currGen &gt;&gt;= (fun text -&gt; nextGen |&gt; Gen.map ((+) text))。但是,您使用Gen.sequence 的方法更具可读性和惯用性。谢谢!
    猜你喜欢
    • 2020-09-14
    • 2013-05-14
    • 2012-03-06
    • 2017-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    相关资源
    最近更新 更多