Why does F#'s printfn work with literal strings, but not values of type string?
Asked Answered
T

2

18

In the following F# code; I would expect that the printfn is being called three times; each with a string. However, the bottom line does not compile (The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>').

What is it about the first two lines that means this can work? Aren't they just strings too?

open System

printfn ("\r\n") // Works
printfn ("DANNY") // Works
printfn (DateTime.Now.ToLongTimeString()) // Doesn't compile
Tiffaneytiffani answered 31/8, 2013 at 19:12 Comment(2)
I don't get why, since there is printf to print format, why not a print that just prints without formatting? It's easy enough to define as let print (x : string) = printf "%s" x but that seems really silly and possibly inefficient.Dustcloth
There is an approved suggestion to add print as an alias of Console.WriteLine. github.com/fsharp/fslang-suggestions/issues/1092Colloquium
R
22

The F# compiler statically analyses the format strings you pass to printfn to check that the arguments you pass are valid for the format specifiers you use. For example, the following does not compile:

printfn "%d" "some value"

since string is not compatible with the %d format specifier. The compiler converts valid format strings into a TextWriterFormat<T>.

It can't do this with arbitrary strings, and since it does not do the conversion, you get the type error above.

You can do the conversion yourself however using Printf.TextWriterFormat. For example, for a format string requiring a string and an int you can use:

let f = Printf.TextWriterFormat<string -> int -> unit>("The length of '%s' is: %d")
printfn f "something" 9

Since your string has no format placeholders, you can do:

let f = Printf.TextWriterFormat<unit>(DateTime.Now.ToLongTimeString())
printfn f
Regenerate answered 31/8, 2013 at 19:21 Comment(4)
Ah; I forgot the compiler was doing magic here; I was thinking about runtime. Makes perfect sense that this only works with strings available at compile time!Tiffaneytiffani
Is the implicit conversion from string to TextWriterFormat<T> something F# does specifically for printf, or can you do this with your own types too?Prattle
"Makes perfect sense that this only works with strings available at compile time!" -- Well, no. let printsd fmt = Printf.TextWriterFormat<string -> int -> unit> fmt |> printfn works. And if fmt doesn't have the right format specifiers, it fails at runtime.Intelligencer
@JoeyAdams See bugsquash.blogspot.com/2010/07/abusing-printfformat-in-f.htmlIntelligencer
H
10

@Lee's answer is correct as per a possible workaround, but it does not describe what happens with your code.

In an expression printf "foo", the "foo" is not a string to be formatted. Instead, it is input formatter by itself. More specifically, it is a string literal used to infer the actual type of the TextWriterFormat<'T>.

The signature of printf is:

printf : TextWriterFormat<'T> -> 'T

Since printfn ("DANNY") does not contain any format specifiers, the F# compiler infers a TextWriterFormat<unit>, and the entire expression becomes printfn ("DANNY") ().

With a variable, it's impossible to statically predict what format specifiers will be there. Consider if ToLongTimeString() method was able to return strings of "%s" or "%d %d %d", what would be the prototype of the returned function?

When inferring the correct type, a string literal works fine, but the variable or let-binding does not work:

let foo1 = "foo"
let bar = printf foo // does not compile
[<Literal>] let foo2 = "foo";; // see update below
let bar = printf foo2 // compiles fine

In any case, it looks much safer to always use format specifiers:

printf "%s" "DANNY"
printf "%s" (DateTime.Now.ToLongTimeString())

Update: don't forget to type double colon ;; after [<Literal>] value to avoid warning FS0058 in VS2013.

Hindi answered 31/8, 2013 at 23:25 Comment(1)
Adding literal within let main argv = ... gives Unexpected symbol [< in expressionDnepropetrovsk

© 2022 - 2024 — McMap. All rights reserved.