1

我编写了一个 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样的?

4

1 回答 1

3

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

浏览模块中的函数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 }) ""

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

于 2017-11-02T21:05:46.007 回答