【问题标题】:Easily getting function names in F#在 F# 中轻松获取函数名称
【发布时间】:2016-09-29 11:29:50
【问题描述】:

在寻找答案时,我发现了几个与我的情况不太相符的问题——所以我会问一个新问题。

我正在为一个数据结构编写一些 FsCheck 测试,我想在我构建的数据结构的每个副本上检查大约 20 个不同的属性。到目前为止,我所做的是为每个属性编写一个谓词,然后我制作了一个谓词列表,我会用List.forall 依次调用它们,如下所示:

module PropertyChecks =
    let ``Tail length plus tree length should equal vector length`` vec =
        treeLength vec.root + Array.length vec.tail = vec.len

    let ``The tail may only be empty iff the vector is empty`` vec =
        (vec.len = 0) = (Array.length vec.tail = 0)

    let ``The tail's length may not exceed tailMax`` vec =
        Array.length vec.tail < tailMax

    let ``If vec.len <= tailMax, all items are in tail and root is empty`` vec =
        (vec.len > tailMax) || (Array.length vec.root = 0)

    // And so on for about 15 more predicates

    let allProperties =
      [
        ``Tail length plus tree length should equal vector length``
        ``The tail may only be empty iff the vector is empty``
        ``The tail's length may not exceed tailMax``
        ``If vec.len <= tailMax, all items are in tail and root is empty``
      ]

    let checkProperties vec =
        allProperties |> List.forall (fun pred -> pred vec)

    // Rest of my code omitted since it's not relevant to the question

我面临的问题是,我预计当一个属性因为我没有正确构建数据结构而失败时,另外两个或三个其他属性会同时失败。我想获取所有失败属性的列表,这意味着在checkProperties 中我想提取失败谓词的 name。我已经看到了多个答案,例如“您无法从作为参数收到的任意 F# 函数中获取 MethodInfo,因为您永远不知道您是否获得了函数本身、lambda 或匿名功能”。但在这里,我不仅知道我有真正的功能,我还知道它们的名字。我可以轻松地将它们的名称复制并粘贴到字符串中,并使 allProperties 成为 (string,function) 元组的列表。但是我已经对复制和粘贴一次(将谓词放入该列表)感到不太满意,我宁愿不这样做两次。

制作(函数名称,函数)元组列表的更好解决方案是什么?我可以将allProperties 和checkProperties 移出PropertyChecks 模块,使其只包含谓词,然后使用反射来遍历该模块吗?

我对整个 .Net 反射系统非常陌生,所以我很可能会遗漏一些明显的东西。如果我错过了,请随时指出显而易见的;我不会感到被侮辱。

【问题讨论】:

  • 在没有带有反射的棘手解决方案的情况下,使用所需级别的 shell 脚本解析此文件不会太难
  • 看了标题,只能想到:lambda, lambda, lambda.

标签: reflection f#


【解决方案1】:

运行 FsCheck 测试的最佳选择是将 FsCheck 与一些单元测试运行器一起使用。测试运行程序负责查找所有属性、运行它们并在出现问题时打印出漂亮的错误日志。

.NET 中两个最常见的与 FsCheck 一起工作的测试运行器是 xUnit 和 NUnit,FsCheck 文档描述了如何使用它们:

在这两种情况下,您都使用 [&lt;Property&gt;] 属性标记属性,测试运行程序将使用它来查找它们并使用 FsCheck 为您运行它们。所以你需要这样的东西:

[<Property>]
let ``Tail length plus tree length should equal vector length`` vec =
    treeLength vec.root + Array.length vec.tail = vec.len

[<Property>]
let ``The tail may only be empty iff the vector is empty`` vec =
    (vec.len = 0) = (Array.length vec.tail = 0)

[<Property>]
let ``The tail's length may not exceed tailMax`` vec =
    Array.length vec.tail < tailMax

[<Property>]
let ``If vec.len <= tailMax, all items are in tail and root is empty`` vec =
    (vec.len > tailMax) || (Array.length vec.root = 0)

【讨论】:

  • 虽然这通常是一个很好的建议并且值得一票,但我针对我的特殊情况选择了另一个答案,因为测试看起来像“对数据结构进行一堆操作,然后验证所有的属性仍然是真实的。”因此,我不想使用[&lt;Property&gt;] 进行测试,而是想编写像let vec' = vec |&gt; push "foo" |&gt; insert 3 "bar" |&gt; pop; checkAllProperties vec' 这样的测试。为此,John Palmer 建议的“hacky bash 脚本”更有用。但是感谢您的好建议;我肯定会在其他测试中使用[&lt;Property&gt;]。
【解决方案2】:

这是一个 hacky bash 脚本,它可能比反射更容易:

#!/bin/bash
echo "let pairs = [|";
cat t.fs | grep let | grep -o "\`\`[^\`]*\`\`" | tr -d \` | awk '{printf "    (\"%s\",``%s``);\n", $0,$0}';
echo "|]"

给出:

let pairs = [|
    ("Tail length plus tree length should equal vector length",``Tail length plus tree length should equal vector length``);
    ("The tail may only be empty iff the vector is empty",``The tail may only be empty iff the vector is empty``);
    ("The tail's length may not exceed tailMax",``The tail's length may not exceed tailMax``);
    ("If vec.len <= tailMax, all items are in tail and root is empty",``If vec.len <= tailMax, all items are in tail and root is empty``);
|]

【讨论】:

  • 这仍然是复制和粘贴,但它是一种自动方法,可确保在我添加新属性或决定更改现有属性的名称时更新属性对。因此,尽管 Tomas Petricek 的回答非常好,但我接受了你的回答,因为它在我的特殊情况下实际上对我有更多帮助。
  • 好吧,你可以使用一个 makefile 来把它变成一个单独的文件,并让它以正确的方式得到更新
猜你喜欢
  • 2015-06-18
  • 2011-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
  • 2014-05-21
  • 2021-04-11
相关资源
最近更新 更多