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)
Related
I looked up the documentation of printfn here and this is what it says:
printfn format
Print to stdout using the given format, and add a newline.
format : TextWriterFormat<'T> The formatter.
Returns: 'T The formatted result.
But if I type the following in FSI
> let v = printfn "Hello";;
Hello
val v : unit = ()
it states that v (the return value of printfn) is of type unit.
This seems inconsistent, but I guess I am missing something here, so can anybody help me out ?
It's not inconsistent. In your example, the format is of type TextWriterFormat<unit>, so the final return type is unit, because 'T is unit.
If you had written let v = printfn "Hello %s", then the type of v would be string -> unit. This is how F# provides type-safe string formatting.
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"
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.
Since I do research with F# (in particular, using F# interactive), I'd like to have switchable "print-when-in-debug" function.
I can do
let dprintfn = printfn
F# interactive says
val dprintfn : (Printf.TextWriterFormat<'a> -> 'a)
and I can use
dprintfn "myval1 = %d, other val = %A" a b
whenever I want in my scripts.
Now I'd like to define dprintfn differently, so that it would ignore all its arguments yet being syntax-compatible with printfn. How?
The closest (yet non-working) variant I have in mind is:
let dprintfn (arg: (Printf.TextWriterFormat<'a> -> 'a)) = ()
but it the following doesn't compile then dprintfn "%A" "Hello", resulting in error FS0003: This value is not a function and cannot be applied.
P.S. I currently use an alias for Debug.WriteLine(...) as work-around, but the question is still interesting for understading F#'s type system.
You can use the kprintf function, which formats a string using the standard syntax, but then calls a (lambda) function you specify to print the formatted string.
For example, the following prints the string if debug is set and otherwise does nothing:
let myprintf fmt = Printf.kprintf (fun str ->
// Output the formatted string if 'debug', otherwise do nothing
if debug then printfn "%s" str) fmt
I've been profiling my application and found that debug formatting causes significant performance issues. Debug formatting occurs on almost every string of the code, due to the nature of the application.
Obviously, this has been caused by kprintf which unconditionally formats and then passes a string to a predicate.
Finally, I came up with the following solution that may be useful for you:
let myprintf (format: Printf.StringFormat<_>) arg =
#if DEBUG
sprintf format arg
#else
String.Empty
#endif
let myprintfn (format: Printf.TextWriterFormat<_>) arg =
#if DEBUG
printfn format arg
#else
()
#endif
Usage is quite simple, and format checking works fine:
let foo1 = myprintf "foo %d bar" 5
let foo2 = myprintf "foo %f bar" 5.0
// can't accept int
let doesNotCompile1 = myprintf "foo %f bar" 5
// can't accept two arguments
let doesNotCompile2 = myprintf "foo %f bar" 5.0 10
// compiles; result type is int -> string
let bar = myprintf "foo %f %d bar" 5.0
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