Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

f# signature matching explained

Tags:

f#

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'

like image 502
cocogorilla Avatar asked Dec 06 '22 23:12

cocogorilla


2 Answers

I think that part of the problem comes from mixing methods (OO style) and functions (FP style).

  • FP style functions have multiple parameters separated by spaces.
  • OO style methods have parens and parameters separated by commas.
  • Methods in other .NET libraries are always called using "tuple" syntax (actually subtly different from tuples though) and a tuple is considered to be one parameter.

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

like image 52
Grundoon Avatar answered Dec 27 '22 22:12

Grundoon


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)
like image 45
Mark Seemann Avatar answered Dec 28 '22 00:12

Mark Seemann