Friedrich Gretz
Friedrich Gretz

Reputation: 535

F# mysterious differences between piping and function application

Premise: I thought that the piping operator is nothing but syntactic sugar, thus x |> f should be exactly the same as f(x).

Analogously, I thought that f (fun x -> foo) is equivalent to let g = fun x -> foo; f g

But apparently there are differences that I do not understand.

Example 1:

static member contents = 
    let files = Directory.EnumerateFiles (__SOURCE_DIRECTORY__+ @"\foo\bar.txt")
    let fileList = List.ofSeq files
    fileList |> List.map (fun f -> TestCaseData(f).SetName(""))

This works fine: TestCaseData expects an arg:obj which is matched by f which in turn in inferred to be a string since fileList is a list of file names. However the following does not work

static member contents = 
    let files = Directory.EnumerateFiles (__SOURCE_DIRECTORY__+ @"\foo\bar.txt")
    let fileList = List.ofSeq files
    List.map (fun f -> TestCaseData(f).SetName("")) fileList

Nothing but the last line has changed. Suddenly f is inferred as obj [] and TestCaseData requires an argument of type obj [] and hence I get an error

Error   1   Type mismatch. Expecting a obj [] list but given a string list    
The type 'obj []' does not match the type 'string'

I would have thought that both snippets are equivalent in produce correct code but only the first does?!

Example 2:

[<TestCase("nonsense", TestName="Nonsense")>]
member x.InvalidInputs str =
    let lexbuf = Microsoft.FSharp.Text.Lexing.LexBuffer<char>.FromString(str)
    Assert.Throws<FatalError> (fun () -> ParsePkg.parse "Dummy path" lexbuf |> ignore)
    |> ignore

Above everything works fine.

[<TestCase("nonsense", TestName="Nonsense")>]
member x.InvalidInputs str =
    let lexbuf = Microsoft.FSharp.Text.Lexing.LexBuffer<char>.FromString(str)
    let ff = fun () -> ParsePkg.parse "Dummy path" lexbuf |> ignore
    Assert.Throws<FatalError> (ff)
    |> ignore

As you see, all I did was pull out the argument of the assertion by first defining let ff = ... (for readability reasons, say) and suddenly the compiler points to the (ff) argument and complains:

Error   2   This expression was expected to have type TestDelegate but here has type unit -> unit

TestDelegate is a type of NUnit that I am using here and it happens to coincide with unit->unit so I'd assume it would be unified anyway but that does not even matter. Why at all is it possible that the type changes, since again I believe to have done a purely syntactic replacement?!

Upvotes: 3

Views: 123

Answers (1)

Rune FS
Rune FS

Reputation: 21752

type inferences is done sequential top to bottom. So in the first case fileList is the first lexical argument. The information that the fileList is a list of strings is then used in the piping expression. To know whether stringis a legal type for f the signature of TestCaseData is used. As commented, based on the error message TestCaseData probably accepts [<Params>] obj [] making a single string argument valid.

In the second version there's no information to use when determining the type of f other than the signature of TestCaseData and therefor f is inferred to be of type obj []

Similar is true in the other example. Just the other way around. Pulling out the function removes the information that it's supposed to be of type TestDelegate.

At the lexical point the only information available is that it's a function of type unit->unit.

When the function is used at a program point where a TestDelegate is required. The type inference tests whether the function can be used as a TestDelegate and if so, infers the type to be TestDelegate

Upvotes: 7

Related Questions