How to have useful assertion-failure messages for FsUnit? - f#

I'm using FsUnit 2.3.2 and I'm not happy with the failure messages. See the examples below:
[<Test>]
let ``test 1``() =
[1; 3]
|> should equal [1;2]
... gives me the not-so-helpful message:
Expected and actual are both
Microsoft.FSharp.Collections.FSharpList`1[System.Int32]
at FsUnit.TopLevelOperators.should[a,a](FSharpFunc`2 f, a x, Object y)
in d:\GitHub\FsUnit\src\FsUnit.NUnit\FsUnit.fs:line 44 at Program.test
1() in F:\work\playground\fsunit\fsunit\Program.fs:line 9
A workaround that I found was to use arrays instead of lists:
[<Test>]
let ``test 2``() =
[|1; 4|]
|> should equal [|1;2|]
...produces
Expected and actual are both System.Int32[2]
Values differ at index [1]
Expected: 2
But was: 4
A second problem is if I have an ADT defined
type MyT =
A of int
| B of string
[<Test>]
let ``test 4``() =
A 10
|> should equal (B "abc")
...gives me the message:
Expected: Program+MyT+B
But was: Program+MyT+A
...which I can workaround by implementing ToString for MyT like this:
override this.ToString() = match this with
| A i -> sprintf "A(%d)" i
| B s -> sprintf "B(%s)" s
...which will lead to a good message:
Expected: B(abc)
But was: A(10)
...but I would like fsunit to just render MyT values the way (sprintf "%A") does.
Anyway, having to do these workarounds is NOT OK.
How can I obtain useful messages for F# lists without using arrays?
How to obtain useful messages for ADTs?
Is there a good fix for the above issues or should I just drop FsUnit?
Do you have a better recommendation for a unit testing library for F# that doesn't have these issues?

A couple of contenders:
Expecto
[<Tests>]
let tests =
testList "test group" [
testCase "strings" <| fun _ ->
let subject = "Hello World"
Expect.equal subject "Hello world"
"The strings should be equal"
testCase "lists" <| fun _ ->
let expected = [1; 2]
Expect.equal expected [1; 3]
"The lists should be equal"
testCase "DUs" <| fun _ ->
let expected = A 10
Expect.equal expected (B "abc")
]
Output
[19:29:46 INF] EXPECTO? Running tests...
[19:29:46 ERR] test group/strings failed in 00:00:00.
The strings should be equal.
Expected string to equal:
"Hello world"
↑
The string differs at index 6.
"Hello World"
↑
String does not match at position 6. Expected char: 'w', but got 'W'.
[19:29:46 ERR] test group/lists failed in 00:00:00.
The lists should be equal. Actual value was [1; 2] but had expected it to be [1; 3].
[19:29:46 ERR] test group/DUs failed in 00:00:00.
The DUs should be equal. Actual value was A 10 but had expected it to be B "abc".
[19:29:46 INF] EXPECTO! 3 tests run in 00:00:00.0028417 – 0 passed, 0 ignored, 3 failed, 0 errored. ( ರ Ĺ̯ ರೃ )
val it : int = 1
Unquote
[<Test>]
let ``The strings should be equal`` () =
let subject = "Hello World"
subject =! "Hello world"
Result Message:
"Hello World" = "Hello world"
false
[<Test>]
let ``The lists should be equal`` () =
let expected = [1; 2]
expected =! [1; 3]
Result Message:
[1; 2] = [1; 3]
false
[<Test>]
let ``The DUs should be equal`` () =
let expected = A 10
expected =! (B "abc")
Result Message:
A 10 = B "abc"
false
Unquote's benefit lies in it's Quotations, allowing step-by-step failure messages.
[<Test>]
let ``The arrays should be equal`` () =
let expected = [|0 ; 2 ; 3 ; 4|]
test <# (Array.map ((+) 1) [|0 .. 3|]) = expected #>
Result Message:
Array.map ((+) 1) [|0..3|] = [|0; 2; 3; 4|]
Array.map ((+) 1) [|0; 1; 2; 3|] = [|0; 2; 3; 4|]
[|1; 2; 3; 4|] = [|0; 2; 3; 4|]
false

Related

How to replace an element of a string at a given index

string s = "foo.bar"
s[s.LastIndexOf(".")] = "-"
It sounds dead simple in c-like languages, but drives me nuts in F#
my code:
let sb = new StringBuilder(s)
sb.[s.LastIndexOf(".")] <- '-'
let s = sb.ToString()
Is there more elegant way to do this? Like using |> ? I don't want to explicitly declare a new variable sb.
Alternatively, you can do this as follows:
let s = "foo.bar"
let index = s.LastIndexOf('.')
let s1 = s |> String.mapi(fun i x -> if i=index then '-' else x)
s1 |> printfn "%A"
Print: "foo-bar"
Link: https://dotnetfiddle.net/5FjFR1
There are already good suggestions here. Here's another way to do it:
let s = "foo.bar"
let idx = s.LastIndexOf '.'
let replaced = s.Substring(0, idx) + "-" + s.Substring(idx + 1)
You could work with char array directly instead of wrapped with StringBuilder.
let replaceAt i c (s: string) =
let arr = s.ToCharArray()
arr.[i] <- c
String arr
"foo.bar" |> replaceAt 3 '-'
Some tests comparing it with this one using mapi—
let replaceAt2 index x s = s |> String.mapi (fun i x -> if i=index then '-' else x)
let test f =
let rec loop n =
if n > 0 then
let x = "foo.bar" |> f 3 '-'
loop (n - 1)
loop 10000000
test replaceAt // Real: 00:00:01.188, CPU: 00:00:01.201, GC gen0: 168, gen1: 168, gen2: 0
test replaceAt2 // Real: 00:00:05.587, CPU: 00:00:05.584, GC gen0: 275, gen1: 275, gen2: 0
Just for the fun of it I tried a "more" functional approach with List.foldBack. Try the code below for yourself at .NET Fiddle.
let replaceLastOf candidate replacement l =
let replaceAndPrepend x (xs, found) =
if not found && x = candidate
then (replacement::xs, true)
else (x::xs, found)
fst <| List.foldBack replaceAndPrepend l ([], false)
let replaceLastCharOf candidate replacement (str:string) =
List.ofSeq str
|> replaceLastOf candidate replacement
|> Array.ofList
|> System.String.Concat
Usage:
printfn "%A" (replaceLastOf 1 9 [1;3;1;4;1])
printfn "%A" (replaceLastCharOf '.' '-' "f.oo.bar")
Output:
[1; 3; 1; 4; 9]
"f.oo-bar"

Need to return seq<R> instead of seq<seq<R>>?

The following function files returns seq<seq<R>>. How to make it return seq<R> instead?
type R = { .... }
let files = seqOfStrs |> Seq.choose(fun s ->
match s with
| Helper.ParseRegex "(\w+) xxxxx" month ->
let currentMonth = .....
if currentMonth = month.[0] then
doc.LoadHtml(s)
Some (
doc.DucumentNode.SelectNodes("....")
|> Seq.map(fun tr ->
{ ..... } ) //R. Some code return record type R. Omitted
)
else
printfn "Expect %s found %s." currentMonth month.[0]
None
| _ ->
printfn "No '(Month) Payment Data On Line' prompt."
None
Your snippet is incomplete and so we can't give you a fully working answer. But:
Your code is using Seq.choose and you are returning either None or Some with collection of values. Then you get a sequence of sequences...
You can use Seq.collect which flattens the sequences and replace None with an empty sequence and Some with just the sequence.
Something along those lines (untested):
let files = seqOfStrs |> Seq.collect (fun s ->
match s with
| Helper.ParseRegex "(\w+) xxxxx" month ->
let currentMonth = .....
if currentMonth = month.[0] then
doc.LoadHtml(s)
doc.DucumentNode.SelectNodes("....")
|> Seq.map(fun tr ->
{ ..... } ) //R. Some code return record type R. Omitted
else
printfn "Expect %s found %s." currentMonth month.[0]
Seq.empty
| _ ->
printfn "No '(Month) Payment Data On Line' prompt."
Seq.empty )
The other options like adding Seq.concat or Seq.collect id to the end of the pipeline would obviously work too.
You want to pipe the whole thing to Seq.collect.
for example,
files |> Seq.collect id
You can use an F# sequence expresssion to flatten the seq of seqs into a seq. Say you have:
> let xss = seq { for i in 1 .. 2 -> seq { for j in 1 .. 2 -> i * j } };;
val xss : seq<seq<int>>
> xss;;
val it : seq<seq<int>> = seq [seq [1; 2]; seq [2; 4]]
Then you can do:
> seq { for x in xss do yield! x };;
val it : seq<int> = seq [1; 2; 2; 4]
Behind the scenes, the sequence expression is doing the same thing as Seq.collect, just in a more syntax-sugary way.

Magic sprintf function - how to wrap it?

I am trying to wrap a call to sprintf function. Here's my attempt:
let p format args = "That was: " + (sprintf format args)
let a = "a"
let b = "b"
let z1 = p "A %s has invalid b" a
This seems to work, output is
val p : format:Printf.StringFormat<('a -> string)> -> args:'a -> string
val a : string = "a"
val b : string = "b"
val z1 : string = "That was: A a has invalid b"
But it wouldn't work with more than one arg:
let z2 = p "A %s has invalid b %A" a b
I get compile-time error:
let z2 = p "A %s has invalid b %A" a b;;
---------^^^^^^^^^^^^^^^^^^^^^^^^^^^
stdin(7,10): error FS0003: This value is not a function and cannot be applied
How can I create a single function which would work with any number of args?
UPD. Tomas has suggested to use
let p format = Printf.kprintf (fun s -> "This was: " + s) format
It works indeed. Here's an example
let p format = Printf.kprintf (fun s -> "This was: " + s) format
let a = p "something like %d" 123
// val p : format:Printf.StringFormat<'a,string> -> 'a
// val a : string = "This was: something like 123"
But the thing is that main purpose of my function is to do some work except for formatring, so I tried to use the suggested code as follows
let q format =
let z = p format // p is defined as suggested
printf z // Some work with formatted string
let z = q "something like %d" 123
And it doesn't work again:
let z = q "something like %d" 123;;
----------^^^^^^^^^^^^^^^^^^^
stdin(30,15): error FS0001: The type ''c -> string' is not compatible with the type 'Printf.TextWriterFormat<('a -> 'b)>'
How could I fix it?
For this to work, you need to use currying - your function p needs to take the format and return a function returned by one of the printf functions (which can then be a function taking one or more arguments).
This cannot be done using sprintf (because then you would have to propagate the arguments explicitly. However, you can use kprintf which takes a continuation as the first argument::
let p format = Printf.kprintf (fun s -> "This was: " + s) format
The continuation is called with the formatted string and so you can do whatever you need with the resulting string before returning.
EDIT: To answer your extended question, the trick is to put all the additional work into the continuation:
let q format =
let cont z =
// Some work with formatted string
printf "%s" z
Printf.kprintf cont format

How do i write the classic high/low game in F#?

I was reading up on functional languages and i wondered how i would implement 'tries' in a pure functional language. So i decided to try to do it in F#
But i couldnt get half of the basics. I couldnt figure out how to use a random number, how to use return/continue (at first i thought i was doing a multi statement if wrong but it seems like i was doing it right) and i couldnt figure out how to print a number in F# so i did it in the C# way.
Harder problems is the out param in tryparse and i still unsure how i'll do implement tries without using a mutable variable. Maybe some of you guys can tell me how i might correctly implement this
C# code i had to do last week
using System;
namespace CS_Test
{
class Program
{
static void Main(string[] args)
{
var tries = 0;
var answer = new Random().Next(1, 100);
Console.WriteLine("Guess the number between 1 and 100");
while (true)
{
var v = Console.ReadLine();
if (v == "q")
{
Console.WriteLine("you have quit");
return;
}
int n;
var b = Int32.TryParse(v, out n);
if (b == false)
{
Console.WriteLine("This is not a number");
continue;
}
tries++;
if (n == answer)
{
Console.WriteLine("Correct! You win!");
break;
}
else if (n < answer)
Console.WriteLine("Guess higher");
else if (n > answer)
Console.WriteLine("Guess lower");
}
Console.WriteLine("You guess {0} times", tries);
Console.WriteLine("Press enter to exist");
Console.ReadLine();
}
}
}
The very broken and wrong F# code
open System;
let main() =
let tries = 0;
let answer = (new Random()).Next 1, 100
printfn "Guess the number between 1 and 100"
let dummyWhileTrue() =
let v = Console.ReadLine()
if v = "q" then
printfn ("you have quit")
//return
printfn "blah"
//let b = Int32.TryParse(v, out n)
let b = true;
let n = 3
if b = false then
printfn ("This is not a number")
//continue;
//tries++
(*
if n = answer then
printfn ("Correct! You win!")
//break;
elif n < answer then
printfn ("Guess higher")
elif n>answer then
printfn ("Guess lower")
*)
dummyWhileTrue()
(Console.WriteLine("You guess {0} times", tries))
printfn ("Press enter to exist")
Console.ReadLine()
main()
Welcome to F#!
Here's a working program; explanation follows below.
open System
let main() =
let answer = (new Random()).Next(1, 100)
printfn "Guess the number between 1 and 100"
let rec dummyWhileTrue(tries) =
let v = Console.ReadLine()
if v = "q" then
printfn "you have quit"
0
else
printfn "blah"
let mutable n = 0
let b = Int32.TryParse(v, &n)
if b = false then
printfn "This is not a number"
dummyWhileTrue(tries)
elif n = answer then
printfn "Correct! You win!"
tries
elif n < answer then
printfn "Guess higher"
dummyWhileTrue(tries+1)
else // n>answer
printfn "Guess lower"
dummyWhileTrue(tries+1)
let tries = dummyWhileTrue(1)
printfn "You guess %d times" tries
printfn "Press enter to exit"
Console.ReadLine() |> ignore
main()
A number of things...
If you're calling methods with multiple arguments (like Random.Next), use parens around the args (.Next(1,100)).
You seemed to be working on a recursive function (dummyWhileTrue) rather than a while loop; a while loop would work too, but I kept it your way. Note that there is no break or continue in F#, so you have to be a little more structured with the if stuff inside there.
I changed your Console.WriteLine to a printfn to show off how to call it with an argument.
I showed the way to call TryParse that is most like C#. Declare your variable first (make it mutable, since TryParse will be writing to that location), and then use &n as the argument (in this context, &n is like ref n or out n in C#). Alternatively, in F# you can do like so:
let b, n = Int32.TryParse(v)
where F# lets you omit trailing-out-parameters and instead returns their value at the end of a tuple; this is just a syntactic convenience.
Console.ReadLine returns a string, which you don't care about at the end of the program, so pipe it to the ignore function to discard the value (and get rid of the warning about the unused string value).
Here's my take, just for the fun:
open System
let main() =
let answer = (new Random()).Next(1, 100)
printfn "Guess the number between 1 and 100"
let rec TryLoop(tries) =
let doneWith(t) = t
let notDoneWith(s, t) = printfn s; TryLoop(t)
match Console.ReadLine() with
| "q" -> doneWith 0
| s ->
match Int32.TryParse(s) with
| true, v when v = answer -> doneWith(tries)
| true, v when v < answer -> notDoneWith("Guess higher", tries + 1)
| true, v when v > answer -> notDoneWith("Guess lower", tries + 1)
| _ -> notDoneWith("This is not a number", tries)
match TryLoop(1) with
| 0 -> printfn "You quit, loser!"
| tries -> printfn "Correct! You win!\nYou guessed %d times" tries
printfn "Hit enter to exit"
Console.ReadLine() |> ignore
main()
Things to note:
Pattern matching is prettier, more concise, and - I believe - more idiomatic than nested ifs
Used the tuple-return-style TryParse suggested by Brian
Renamed dummyWhileTrue to TryLoop, seemed more descriptive
Created two inner functions doneWith and notDoneWith, (for purely aesthetic reasons)
I lifted the main pattern match from Evaluate in #Huusom's solution but opted for a recursive loop and accumulator instead of #Hussom's (very cool) discriminate union and application of Seq.unfold for a very compact solution.
open System
let guessLoop answer =
let rec loop tries =
let guess = Console.ReadLine()
match Int32.TryParse(guess) with
| true, v when v < answer -> printfn "Guess higher." ; loop (tries+1)
| true, v when v > answer -> printfn "Guess lower." ; loop (tries+1)
| true, v -> printfn "You won." ; tries+1
| false, _ when guess = "q" -> printfn "You quit." ; tries
| false, _ -> printfn "Not a number." ; loop tries
loop 0
let main() =
printfn "Guess a number between 1 and 100."
printfn "You guessed %i times" (guessLoop ((Random()).Next(1, 100)))
Also for the fun of if:
open System
type Result =
| Match
| Higher
| Lower
| Quit
| NaN
let Evaluate answer guess =
match Int32.TryParse(guess) with
| true, v when v < answer -> Higher
| true, v when v > answer -> Lower
| true, v -> Match
| false, _ when guess = "q" -> Quit
| false, _ -> NaN
let Ask answer =
match Evaluate answer (Console.ReadLine()) with
| Match ->
printfn "You won."
None
| Higher ->
printfn "Guess higher."
Some (Higher, answer)
| Lower ->
printfn "Guess lower."
Some (Lower, answer)
| Quit ->
printfn "You quit."
None
| NaN ->
printfn "This is not a number."
Some (NaN, answer)
let main () =
printfn "Guess a number between 1 and 100."
let guesses = Seq.unfold Ask ((Random()).Next(1, 100))
printfn "You guessed %i times" (Seq.length guesses)
let _ = main()
I use an enumeration for state and Seq.unfold over input to find the result.

Why doesn't my code compile?

let sub (m:double[],n:double[]) : double[]=
[| for i = 0 to Array.length m -1 do m.[i]-n.[i] |]
Error 1 This value is not a function and cannot be applied E:\MyDocuments\Visual Studio 2010\Projects\curve intersection\newton\Module1.fs 27 21 newton
But, this is ok:
let a = [| "a"; "b"; "c"; "d"; "e"; "f" |]
for i = 0 to Array.length a - 1 do
System.Console.WriteLine(a.[i])
Spaces around a minus sign matter:
f -1 // means f(-1)
calls the function f with an argument of -1 (unary minus). Whereas
n - 1
and
n-1
are subtraction.
The compiler error reflects that
Array.length m -1
parses as
(Array.length m)(-1)
as though it is expecting the first expression to return a function, which will then be applied to the value -1. Since length actually returns an int, you get the error message that says that an integer is not a function and cannot be applied to the argument -1.
This compiles:
let sub (m:double[], n:double[]) : double[] =
[| for i = 0 to Array.length m - 1 do yield m.[i] - n.[i] |]
The format of your list/array comprehension is wrong.
you either use -> as a short cut:
let a = [1;2;3]
[| for i in a -> i |]
or formally write yield:
[| for i in a do yield i |]

Resources