The F# library provides a variety of functions (based on the printf functions found in OCaml) that produce formatted text.
| printf | outputs formatted text to the console using the stdout stream. |
| printfn | outputs formatted text suffixed with a new-line character to the console using the stdout stream. |
| eprintf | outputs formatted text to the console using the stderr stream. |
| eprintfn | outputs formatted text suffixed with a new-line character to the console using the stderr stream. |
| sprintf | returns formatted text as a string. |
| bprintf | appends formatted text to a System.Text.StringBuilder. |
| fprintf | writes formatted text to a System.IO.TextWriter. |
| fprintfn | writes formatted text suffixed with a new-line character to a System.IO.TextWriter. |
| twprintf | writes formatted text to a System.IO.TextWriter. |
| twprintfn | writes formatted text suffixed with a new-line character to a System.IO.TextWriter. |
The functions above are especially powerful because the F# compiler will type-check the format arguments and give design-time errors.

While this set of functions covers most of the cases where formatted text is needed, there is one glaring omission: debug output. If we want to pass formatted text to System.Diagnostics.Debug.Write, we have to do it using sprintf like so:
System.Diagnostics.Debug.Write(sprintf "The answer = %d" 42)
Obviously, that's not ideal. What we would really like is a function that behaves exactly like the other printf functions but writes debug output. Fortunately, extensibility has been built into the F# library, making it possible to create additional formatting functions that benefit from the same sweet type-checking. So, how do we go about doing that? The trick is to wrap our functions around a special F# library function, ksprintf.
ksprintf is similar to sprintf in that it formats text as a string. However, instead of returning that string to the caller, it passes the string into a continuation.
I haven't covered the broad topic of continuations yet because a) they can be pretty eye-crossing when presented plainly, and b) there are already fantastic treatments out there. The basic idea is to pass a lambda1 (the so-called continuation) into a computation that will be executed when the computation is finished. Really. That's it. This is a simple idea, but it can become complicated quickly. However, in the case of ksprintf it remains simple. We'll pass a lambda that calls Debug.Write with the final string after it has been formatted.
module Debug =
open System.Diagnostics
let writef fmt = Printf.ksprintf (fun s -> Debug.Write(s)) fmt
let writefn fmt = Printf.ksprintf (fun s -> Debug.WriteLine(s)) fmt
In fact, the wrapper lambdas are redundant because Debug.Write and Debug.WriteLine have overloads that match the expected signature. We can simply pass the functions themselves and remove the wrappers:
module Debug =
open System.Diagnostics
let writef fmt = Printf.ksprintf Debug.Write fmt
let writefn fmt = Printf.ksprintf Debug.WriteLine fmt
Just drop that code into an F# project, and you'll be writing debug output in a beautiful, concise F# style.
Debug.writef "The answer = %d" 42
You'll even get helpful design-time type errors:

Now it's just a matter of convincing the F# team to add it to the libraries. 
1Did It With .NET readers probably already know that "lambda" = "anonymous function".