Reputation: 1865
I am running into difficulty with F# in numerous scenarios. I believe I'm not grasping some fundamental concepts. I'm hoping someone can track my reasoning and figure out the (probably many) things I'm missing.
Say I'm using Xunit. What I'd like to do is, provided two lists, apply the Assert.Equal
method pairwise. For instance:
Open Xunit
let test1 = [1;2;3]
let test2 = [1;2;4]
List.map2 Assert.Equal test1 test2
The compiler complains that the function Equal
does not take one parameter. As far as I can tell, shouldn't map2
be providing it 2 parameters?
As a sanity check, I use the following code in f# immediate:
let doequal = fun x y -> printf "result: %b\n" (x = y)
let test1 = [1;2;3]
let test2 = [1;2;4]
List.map2 doequal test1 test2;;
This seems identical. doequal
is a lambda taking two generic parameters and returning unit. List.map2
hands each argument pairwise into the lambda and I get exactly what I expected as output:
result: true
result: true
result: false
So what gives? Source shows Xunit.Equal
has signature public static void Equal<T>(T expected, T actual)
. Why won't my parameters map right over the method signature?
EDIT ONE I thought two variables x and y vs a tuple (x, y) could construct and deconstruct interchangeably. So I tried two options and got different results. It seems the second may be further along than the first.
List.map2 Assert.Equal(test1, test2)
The compiler now complains that 'Successive arguments should be separated spaces or tupled'
List.map2(Assert.Equal(test1, test2))
The compiler now complains that 'A unique overload method could not be determined... A type annotation may be needed'
Upvotes: 4
Views: 291
Reputation: 13577
It's all there in the type signatures.
The signature for Assert.Equals
is something along the lines of 'a * 'a -> unit
. List.map2
expects a 'a -> 'b -> 'c
.
They just don't fit together.
List.map2 (fun x y -> Assert.Equal(x,y)) test1 test2
- works because the lambda wrapping Equals
has the expected signature.
List.zip test1 test2 |> List.map Assert.Equal
- works because you now have a single list of tuples, and since List.map
wants an 'a -> 'b
function (where 'a
is now a tuple), Assert.Equal
is now fair game.
It's simply not true that two values and a tuple are implicitly interchangeable. At least not as far as F# the language is concerned, or the underlying IL representation is concerned. You can think that it's that way when you call into F# code from, say, C# - an 'a -> 'b -> 'c
function there is indeed called the same way syntactically as an 'a * 'b -> 'c
function - but this is more of an exception than a rule.
Upvotes: 2
Reputation: 4280
According to its signature Xunit.Assert.Equal()
takes a single 2 values tuple parameter
Upvotes: 1
Reputation: 2764
I think that part of the problem comes from mixing methods (OO style) and functions (FP style).
The F# compiler tries to handle both approaches, but needs some help occasionally.
One approach is to "wrap" the OO method with an FP function.
// wrap method call with function
let assertEqual x y = Assert.Equal(x,y)
// all FP-style functions
List.map2 assertEqual test1 test2
If you don't create a helper function, you will often need to convert multiple function parameters to one tuple when calling a method "inline" with a lambda:
List.map2 (fun x y -> Assert.Equal(x,y)) test1 test2
When you mix methods and functions in one line, you often get the "Successive arguments should be separated" error.
printfn "%s" "hello".ToUpper()
// Error: Successive arguments should be separated
// by spaces or tupled
That's telling you that the compiler is having problems and needs some help!
You can solve this with extra parens around the method call:
printfn "%s" ("hello".ToUpper()) // ok
Or sometimes, with a reverse pipe:
printfn "%s" <| "hello".ToUpper() // ok
The wrapping approach is often worth doing anyway so that you can swap the parameters to make it more suitable for partial application:
// wrap method call with function AND swap params
let contains searchFor (s:string) = s.Contains(searchFor)
// all FP-style functions
["a"; "b"; "c"]
|> List.filter (contains "a")
Note that in the last line I had to use parens to give precedence to contains "a"
over List.filter
Upvotes: 7
Reputation: 233150
public static void Equal<T>(T expected, T actual)
doesn't take two parameters - it takes one parameter, which is a tuple with two elements: (T expected, T actual)
.
Try this instead:
List.map2 Assert.Equal(test1, test2)
Upvotes: 5