rmunn
rmunn

Reputation: 36738

Easily getting function names in F#

While searching for an answer, I've found several questions that don't quite match my situation — so I'll ask a new one.

I am writing some FsCheck tests for a data structure, where I want to check about twenty different properties on each copy of the data structure I construct. What I've done so far is to write a predicate for each property, then I've made a list of the predicates and I'll call them each in turn with List.forall, like so:

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

The problem I'm facing is that I expect that when one property fails because I didn't construct the data structure properly, two or three other properties will fail at the same time. I'd like to get a list of all failing properties, which means that in checkProperties I'd like to extract the name of the failing predicate. I've seen multiple answers along the lines of "You can't get the MethodInfo from an arbitrary F# function that you received as an argument, because you never know whether you got the function itself, or a lambda, or an anonymous function". But here, I not only know that I have real functions, I know what their names are. I could easily copy and paste their names into strings, and make allProperties a list of (string,function) tuples. But I'm already feeling not quite happy with having copied-and-pasted once (to put the predicates into that list), and I'd rather not do it twice.

What's a better solution that making a list of (function name, function) tuples? Could I move allProperties and checkProperties out of the PropertyChecks module so that it contains nothing but predicates, and then use reflection to walk that module?

I'm very new to the entire .Net reflection system, so I might well be missing something obvious. Please feel free to point out the obvious if I'm missing it; I won't feel insulted.

Upvotes: 3

Views: 99

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243096

The best option for running FsCheck tests is to use FsCheck together with some unit test runner. The test runner takes care of finding all the properties, running them and printing nice error logs when something goes wrong.

The two most common test runners in .NET that both work with FsCheck are xUnit and NUnit and the FsCheck documentation describes how to use them:

In both cases, you mark the properties with the [<Property>] attribute and the test runner will use this to find them and run them using FsCheck for you. So you'll need something like:

[<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)

Upvotes: 5

John Palmer
John Palmer

Reputation: 25526

Here is a hacky bash script that might be easier than reflection:

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

which gives:

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``);
|]

Upvotes: 2

Related Questions