Matthias Herrmann
Matthias Herrmann

Reputation: 2790

F# Result Unit Test Fails without type annotation - Can I avoid the type annotation in this case?

I'm doing some F# exercises and the task is to implement a function called clean which takes a string input and returns a parsed number.

let clean input = 
    input
    |> Seq.filter Char.IsDigit
    |> Seq.fold (fun acc cur ->
                    if acc = "" && cur = '1' then
                        acc
                    else acc + string cur) ""
    |> uint64
    |> Ok

This is my temporary solution. But the unit test bellow failed:

[<Fact>]
let ``Cleans the number`` () =
    let expected: Result<uint64,string> = Ok 2234567890UL
    let parsed = clean "(223) 456-7890"
    parsed |> should equal expected

It fails:

FsUnit.Xunit+MatchException : Exception of type 'FsUnit.Xunit+MatchException' was thrown. Expected: Equals Ok 2234567890UL Actual: was Microsoft.FSharp.Core.FSharpResult2[System.UInt64,System.Object]
at FsUnit.Xunit.Assert.That.Static[a](a actual, IMatcher
1 matcher)
at PhoneNumberTest.Cleans numbers with dots() in

So I added an type annotation: let clean input: Result<uint64,string> // function body is the same

Can I avoid the type annotation and what is the technical reason that the first solution failed?

Upvotes: 1

Views: 226

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243051

The problem here is that compiler cannot infer the type that your Result would have in the case of a failure from just the success value Ok 2234567890UL. When it does not know, it just uses obj and so the default inferred type is Result<uint64, obj>. Comparing objects of different types is always false.

This is somewhat unfortunate, because you are comparing the two values and so, they have to have the same type. The FsUnit library which provides should and equal operations is not strictly typed and allows you to compare values of different types. You can write:

1 |> should equal "1"

There is a library FsUnitTyped which implements a typed version of FsUnit. I don't have experience with this, but it looks like you could use it and write:

[<Fact>]
let ``Cleans the number`` () =
    let expected = Ok 2234567890UL
    let parsed = clean "(223) 456-7890"
    parsed |> shouldEqual expected

This would solve your problem - the compiler could infer that the type of expected has to be the same as the type of parsed, so it would automatically use the correct type.

Upvotes: 3

nilekirk
nilekirk

Reputation: 2383

Without the type annotation, your clean function is inferred to have the generic type seq<char> -> Result<uint64,'a>, so in the case you need the type annotation.

The reason for the compiler to infer a generic type is that you always return the OK branch of the Result<'ok,'error> type.

As also the name of your function suggest, maybe Result is not the most sutiable return type here?

Upvotes: 3

Related Questions