F#: Composing sprintf with a string -> unit function to allow formatting - f#

There's information out there on how to do custom processing on a format and its parts. I want to do something a bit simpler, specifically, I want to do something to the effect of:
let writelog : string -> unit = ... // write the string to the log
let writelogf = sprintf >> writelog // write a formatted string to the log
I'm not too surprised that the compiler gets confused by this, but is there any way to get it to work?

The simplest way to define your own function that takes a formatting string like printf is to use Printf.kprintf function. The first argument of kprintf is a function that is used to display the resulting string after formatting (so you can pass it your writelog function):
let writelog (s:string) = printfn "LOG: %s" s
let writelogf fmt = Printf.kprintf writelog fmt
The fmt parameter that is passed as a second argument is the special format string. This works better than jpalmer's solution, because if you specify some additional arguments, they will be directly passed to kprintf (so the number of arguments can depend on the formatting string).
You can write:
> writelogf "Hello";;
LOG: Hello
> writelogf "Hello %d" 42;;
LOG: Hello 42

This works
> let writelog = fun (s:string) -> printfn "%s" s;;
val writelog : string -> unit
> let writelogf arg = sprintf arg >> writelog;;
val writelogf : Printf.StringFormat<('a -> string)> -> ('a -> unit)
> writelogf "hello %s" "world";;
hello world
val it : unit = ()
>
(session is from FSI)
key is in the extra argument to writelogf

Related

Writing unit tests against PrintfFormat

I have a type that I'm trying to understand by writing unit tests against it, however I can't reason what to do with PrintfFormat
type ValueFormat<'p,'st,'rd,'rl,'t,'a> = {
format: PrintfFormat<'p,'st,'rd,'rl,'t>
paramNames: (string list) option
handler: 't -> 'a
}
with
static member inline construct (this: ValueFormat<_,_,_,_,_,_>) =
let parser s =
s |> tryKsscanf this.format this.handler
|> function Ok x -> Some x | _ -> None
let defaultNames =
this.format.GetFormatterNames()
|> List.map (String.replace ' ' '_' >> String.toUpperInvariant)
|> List.map (sprintf "%s_VALUE")
let names = (this.paramNames ?| defaultNames) |> List.map (sprintf "<%s>")
let formatTokens = this.format.PrettyTokenize names
(parser, formatTokens)
I feel confident that I can figure everything out but PrintfFormat is throwing me with all those generics.
The file I'm looking at for the code I want to unit test is here for the FSharp.Commandline framework.
My question is, what is PrintfFormat and how should it be used?
A link to the printf.fs file is here. It contains the definition of PrintfFormat
The PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple> type, as defined in the F# source code, has four type parameters:
'Result is the type that your formatting/parsing function produces. This is string for sprintf
'Printer is a type of a function generated based on the format string, e.g. "%d and %s" will give you a function type int -> string -> 'Result
'Tuple is a tuple type generated based on the format string, e.g. "%d and %s" will give you a tuple type int * string.
'State and 'Residue are type parameters that are used when you have a custom formatter using %a, but I'll ignore that for now for simplicity (it's never needed unless you have %a format string)
There are two ways of using the type. Either for formatting, in which case you'll want to write a function that returns 'Printer as the result. The hard thing about this is that you need to construct the return function using reflection. Here is an example that works only with one format string:
open Microsoft.FSharp.Reflection
let myformat (fmt:PrintfFormat<'Printer,obj,obj,string,'Tuple>) : 'Printer =
unbox <| FSharpValue.MakeFunction(typeof<'Printer>, fun o ->
box (o.ToString()) )
myformat "%d" 1
myformat "%s" "Yo"
This simply returns the parameter passed as a value for %d or %s. To make this work for multiple arguments, you'd need to construct the function recursively (so that it's not just e.g. int -> string but also int -> (int -> string))
In the other use, you define a function that returns 'Tuple and it needs to create a tuple containing values according to the specified formatting string. Here is a small sample that only handles %s and %d format strings:
open FSharp.Reflection
let myscan (fmt:PrintfFormat<'Printer,obj,obj,string,'Tuple>) : 'Tuple =
let args =
fmt.Value
|> Seq.pairwise
|> Seq.choose (function
| '%', 'd' -> Some(box 123)
| '%', 's' -> Some(box "yo")
| _ -> None)
unbox <| FSharpValue.MakeTuple(Seq.toArray args, typeof<'Tuple>)
myscan "%d %s %d"

how can I build a format string with sprintf, in F#?

I'm trying to go from:
sprintf "%3.1f" myNumber
to:
sprintf myFormatter myNumber
which is not possible
I have a situation where number precision depends on some settings, so I would like to be able to create my own formatter string.
I know it can be done with String.Format, but I am curious if there is a F# way with sprintf, or ksprinf; can it be done?
Simple answer
EDIT: Diego Esmerio on F# Slack showed me a simpler way that I honestly never thought of while working out the answer below. The trick is to use PrintfFormat directly, like as follows.
// Credit: Diego. This
let formatPrec precision =
PrintfFormat<float -> string,unit,string,string>(sprintf "%%1.%if" precision)
let x = 15.234
let a = sprintf (formatPrec 0) x
let b = sprintf (formatPrec 1) x
let c = sprintf (formatPrec 3) x
Output:
val formatPrec : precision:int -> PrintfFormat<(float -> string),unit,string,string>
val x : float = 15.234
val a : string = "15"
val b : string = "15.2"
val c : string = "15.234"
This approach is arguably much simpler than the Expr-based approach below. For both approaches, be careful with the formatting string, as it will compile just fine, but break at runtime if it is invalid.
Original answer (complex)
This isn't trivial to do, because functions like sprintf and printfn are compile-time special-case functions that turn your string-argument into a function (in this case of type float -> string).
There are some things you can do with kprintf, but it won't allow the formatting-argument to become a dynamic value, since the compiler still wants to type-check that.
However, using quotations we can build such function ourselves. The easy way is to create quotation from your expression and to change the parts we need to change.
The starting point is this:
> <# sprintf "%3.1f" #>
val it : Expr<(float -> string)> =
Let (clo1,
Call (None, PrintFormatToString,
[Coerce (NewObject (PrintfFormat`5, Value ("%3.1f")), PrintfFormat`4)]),
Lambda (arg10, Application (clo1, arg10)))
...
That may look like a whole lot of mess, but since we only need to change one tiny bit, we can do this rather simply:
open Microsoft.FSharp.Quotations // part of F#
open Microsoft.FSharp.Quotations.Patterns // part of F#
open FSharp.Quotations.Evaluator // NuGet package (with same name)
// this is the function that in turn will create a function dynamically
let withFormat format =
let expr =
match <# sprintf "%3.1f" #> with
| Let(var, expr1, expr2) ->
match expr1 with
| Call(None, methodInfo, [Coerce(NewObject(ctor, [Value _]), mprintFormat)]) ->
Expr.Let(var, Expr.Call(methodInfo, [Expr.Coerce(Expr.NewObject(ctor, [Expr.Value format]), mprintFormat)]), expr2)
| _ -> failwith "oops" // won't happen
| _ -> failwith "oops" // won't happen
expr.CompileUntyped() :?> (float -> string)
To use this, we can now simply do this:
> withFormat "%1.2f" 123.4567899112233445566;;
val it : string = "123.46"
> withFormat "%1.5f" 123.4567899112233445566;;
val it : string = "123.45679"
> withFormat "%1.12f" 123.4567899112233445566;;
val it : string = "123.456789911223"
Or like this:
> let format = "%0.4ef";;
val format : string = "%0.4ef"
> withFormat format 123.4567899112233445566;;
val it : string = "1.2346e+002f"
It doesn't matter whether the format string is now a fixed string during compile time. However, if this is used in performance sensitive area, you may want to cache the resulting functions, as recompiling an expression tree is moderately expensive.

Can I use StringFormat as TextWriterFormat? kfprintf / kprintf usage

I've got function to log to console
Printf.kprintf
(printfn
"[%s][%A] %s"
<| level.ToString()
<| DateTime.Now)
format // fprint to System.Console.Out maybe
but it's using Printf.StringFormat as format and now I want to follow same logic and print it to file.
So I try
Printf.kfprintf
(fun f ->
fprintfn file "[%s][%A] "
<| level.ToString()
<| DateTime.Now
) file (format)
And there are two things I can't understand. Why there is unit -> 'A instead of string -> 'A ? How should I use it? And Can I use my StringFormat here as TextWriterFormat ?
Another trouble with this is that with first snippet I inherit format to string -> 'Result thing but in kfprintf I can't do it because there is unit -> 'Result and format message appears before [x][x] stuff. I guess I can somehow inherit format to f but I can't find good example, the only I found is part of F# compiler:
[<CompiledName("PrintFormatToTextWriter")>]
let fprintf (os: TextWriter) fmt = kfprintf (fun _ -> ()) os fmt
[<CompiledName("PrintFormatLineToTextWriter")>]
let fprintfn (os: TextWriter) fmt = kfprintf (fun _ -> os.WriteLine()) os fmt
But how can I use this unit ? How can I post message after my message?
I don't think you need to use Printf.kfprintf, you can carry on using Printf.kprintf as the inner fprintfn uses the TextWriter.
let logToWriter writer level format =
Printf.kprintf (fprintfn writer "[%s][%A] %s"
<| level.ToString()
<| System.DateTime.Now) format
Also see this for an example of using Printf.kfprintf.

In F#, how do you curry ParamArray functions (like sprintf)?

In F#, how do you curry a function that accepts a variable number of parameters?
I have code like this...(the log function is just an example, the exact implementation doesn't matter)
let log (msg : string) =
printfn "%s" msg
log "Sample"
It gets called throughout the code with sprintf formatted strings, ex.
log (sprintf "Test %s took %d seconds" "foo" 2.345)
I want to curry the sprintf functionality in the log function so it looks like...
logger "Test %s took %d seconds" "foo" 2.345
I've tried something like
let logger fmt ([<ParamArray>] args) =
log (sprintf fmt args)
but I cannot figure out how to pass the ParamArray argument through to the sprintf call.
How is this done in F#?
let log (s : string) = ()
let logger fmt = Printf.kprintf log fmt
logger "%d %s" 10 "123"
logger "%d %s %b" 10 "123" true
The behaviour of printf-like functions in F# is in some way special. They take a format string, which specifies what the expected arguments are. You can use Printf.kprintf as shown by desco to define your own function that takes a format string, but you cannot change the handling of format strings.
If you want to do something like C# params (where the number of arguments is variable, but does not depend on the format string), then you can use ParamArray attribute directly on a member:
open System
type Foo =
static member Bar([<ParamArray>] arr:obj[]) =
arr |> Seq.mapi (fun i v -> printfn "[%d]: %A" i v)
Then you can call Foo.Bar with any number of arguments without format string:
Foo.Bar("hello", 1, 3.14)
This is less elegant for string formatting, but it might be useful in other situations. Unfortunatelly, it will only work with members (and not with functions defined with let)

F# apply sprintf to a list of strings

How can I best create a function (lets call it myPrint) that takes sprintf, a format string, and a list of strings as arguments and produces a result such that each element in the list of strings is applied/folded into sprintf?
i.e.
myPrint (sprintf "one: %s two: %s three: %s") ["first"; "second"; "third"];;
would produce the output
val myPrint : string = "one: first two: second three: third"
Note that format strings in F# are specifically designed to take a statically known number of arguments, so there's not a particularly elegant way to do what you want. However, something like this should work:
let rec myPrintHelper<'a> (fmt:string) : string list -> 'a = function
| [] -> sprintf (Printf.StringFormat<_>(fmt))
| s::rest -> myPrintHelper<string -> 'a> fmt rest s
let myPrint fmt = myPrintHelper<string> fmt
myPrint "one: %s two: %s three: %s" ["first"; "second"; "third"]
This will throw an exception at runtime if the number of "%s"es in your string doesn't match the number of strings in the list.
List.map myPrint ["first"; "second"; "third;"]
Will return a new sprintf'ed List...
To just print out to console... List.iter will iterate a function over each value in a list, and perform that function - but will only work with functions that don't return a value. (i.e. printf...)
map takes an 'T -> 'U - and will convert a list of any format into any other
So
let myPrint in = sprintf "%s" in
would be allowed, but reading your question again - it doesn't do what you want.
- you could use mapi, which adds the index in (int -> 'T -> 'U) and would let you define myPrint as
let myPrint index val = sprintf "%d : %s" index val
Which would return ["1 : first"; "2 : second"; "3 : third"] which is getting closer...
But it looks like you want a single string returned - so either to String.Join on the output of that - or use fold:
let final = List.fold (fun (builder, index) in -> builder.AppendFormat("{0}: {1}", index, in), index + 1) (new StringBuilder()) ["first"; "second"; "third"]

Resources