"Hello" |> printfn generates an error in F#
Asked Answered
A

1

6

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.

Animadvert answered 19/8, 2022 at 11:59 Comment(0)
D
8

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"
Demonstration answered 19/8, 2022 at 13:29 Comment(12)
Ok, this is surely such a surprise. Thanks :) Since I do not like magic, I create a non-magical function let print = fun msg -> printf "%s" msg Probably that will regain integration of the language.Animadvert
@SmoothTraderKen, yes, that'd work. You can simplify that further to be let print = printf "%s". Note that this "magic" is very powerful here, the idea behind it is that the arguments %s, %i etc allow the compiler to infer a type-safe signature. I.e., you'll get a compile error (not a runtime error) if you do printfn "Amount: %i" 42.4, as an int is expected and not a float.Demonstration
Thanks a lot, I will do let printInt = printf "%d" too.Animadvert
That’ll print a decimal ;). If you put them in a class as static members with the same name, you get overload resolution.Demonstration
Abel, oh certainly, that would be much better!! I've just started F# as my main tool, so could you please provide the code for this in your Answer here? I really appreciated your advice here :)Animadvert
@SmoothTraderKen, done. I added an extra section for you (PS: I said in the previous comment that %d prints a decimal. It doesn't, %M does that. Full list is here: learn.microsoft.com/en-us/dotnet/fsharp/language-reference/…)Demonstration
Abel, Thanks a lot! I need to learn this area. Studying your information, so far, I came across a generic solution let print = fun a -> printf "%O" aAnimadvert
By the way, this is a total coincidene. I happen to have responded to you, not knowing it's you in GitHub issue. github.com/fsharp/fslang-suggestions/issues/… stken2050 is me :)Animadvert
Ah, nice! Btw, while printf "%O" could be considered a generic solution (as is printf "%A"), it removes type safety. The whole idea of using printf "%i" and the like is that you introduce type safety for your arguments: printfn "User %s is %i years old" name age. If you'd inverse the arguments, you'd get a compile error. You won't get that with your generic print. Something to keep in mind ;).Demonstration
Thank you for the supplementary information! Actually including this QA, the F# printf mechanism has been tricky for me, and your answer&comments help me a lot. Really appreciated :)Animadvert
is there a way to add those static functions without having to open the module? I suspect not. but it would be super nice. Also, is there a reason to not just use %A?Agribusiness
@AdamB Yes, as %A is often way too verbose and certainly not a one size fits all solution. You can open a module automatically with AutoOpen, but not a type. Though you should be careful about this, it’s often better to be explicit, which allows consumers to pick and choose.Demonstration

© 2022 - 2024 — McMap. All rights reserved.