SmoothTraderKen
SmoothTraderKen

Reputation: 652

"Hello" |> printfn generates an error in F#

https://tryfsharp.fsbolero.io/

printfn "Hello"

works as expected without errors, however, using pipe operator

"Hello" |> printfn

The type 'string' is not compatible with the type 'Printf.TextWriterFormat'

I understood the pipe operator behavior:

f(a) is equivalent to a |> f

Why does the latter generate the error?? Thanks.

Upvotes: 6

Views: 199

Answers (1)

Abel
Abel

Reputation: 57149

Yes. The pipe operator does what you think it does. However, printfn is "special" that it takes a kind of "formattable string" (there are different kinds of these) and the compiler does its magic only when the format string appears as a direct argument.

In other words, your "Hello" in the first example is not really a string, it is a Printf.TextWriterFormat object, magically created by the compiler.

Still, you can do what you want by using an explict format string. This approach you'll see quite a bit in real-world code:

"Hello" |> printfn "%s"

Here, %s means: give me a string. The magic of F# in this case again takes the format string, here "%s", this time with an argument of type string, and turns it into a function.

Note 1: that this "surprise effect" is being considered and the community works towards adding a printn function (i.e., without the f which stands for format) to just take a simple string: https://github.com/fsharp/fslang-suggestions/issues/1092

Note 2: if you do want to pass arguments to printfn around, you can make the type explicit, but this is done very rarely:

let x = Printf.TextWriterFormat<unit> "Hello"
x |> printfn  // now it's legal

Note 3: you might wonder why the compiler doesn't apply its magic to the lh-side of |> as well. The reason is that |> is not an intrinsic to the compiler, but just another overridable operator. Fixing this is therefor technically very hard (there can be any amount of operators on the lh-side), though it has been considered at certain times.


Other alternative

In the comments, the OP suggested that he/she/they didn't like the idea of having to use printfn "%i" and the like, and ended up writing print, printInt etc functions.

If you go that route, you can write your code in a class with only static methods, while using function-style naming. Then just open the static type (this is a new feature in F# 6).

module MyPrinters =
    // create a static type (no constructors)
    type Printers =
        static member print x = printfn "%s" x  // string
        static member print x = printfn "%i" x  // integer
        static member print x = printfn "%M" x  // decimal
        static member print x = printfn "%f" x  // float


module X =
    // open the static type
    open type MyPrinters.Printers

    let f() = print 42
    let g() = print "test"

Upvotes: 8

Related Questions