F#: is no-op printf possible? - f#

I have a couple of wrappers of Printf-family API (e.g. for logging)
type Logger(writer: TextWriter) =
...
member x.Log (fmt: PrintfFormat<'Printer, _, _, _>): 'Printer =
Printf.fprintfn writer fmt
Let's say I want to introduce the notion of "log level" to it, add Logger.Info(), Debug(), Trace() etc. which are basically another layer on top of Logger.Log(), and if logger.Level <- LogLevel.Info then Debug() and Trace() should turn into no-ops. Pretty common requirement, I'd say.
But I can't seem to return a 'Printer-typed value without actually calling any of Printf-family API.
type LogLevel = TRACE | DEBUG | INFO
type LevelLogger(writer: TextWriter, level: LogLevel) =
inherit Logger(writer)
member x.Info (fmt: PrintfFormat<'Printer, _, _, _>): 'Printer =
if level <= LogLevel.INFO then
x.Log fmt
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The type variable 'Printer has been constrained to be type 'unit'.
let l0 = Logger(Console.Error)
l0.Log "foo %d %s %f" 1 "foo" 1.23
let l1 = LevelLogger(Console.Error, LogLevel.INFO)
l1.Info "foo %d %s %f" 1 "foo" 1.23
^^^^^^^^^^^^^^^^^^^^^^
error FS0001: This expression was expected to have type
'unit'
but here has type
''a -> string -> 'b -> unit'
error FS0003: This value is not a function and cannot be applied.
It would be ideal if I could write
member x.Info (fmt: PrintfFormat<'Printer, _, _, _>): 'Printer =
if level <= LogLevel.INFO then x.Log fmt
else Printf.noop fmt
But does there exist such a utility API? Or can we write one?
Workarounds just for "getting things done" that I'm aware of:
fprintfn TextWriter.Null, I believe, still formats all args and can be unnecessarily expensive
kbprintfn with a continuation that conditionally calls writer.WriteLine(sb) has the same issue
In C++ codebase, this kind of dynamic config is often complemented with preprocessor directives (#if ...), like, enabling TRACE level only under -DDEBUG; but I don't know if we can write an ergonomic API with them in F#.
Also, PrintfFormat is always allocated out of a format string anyways, then the "no-op" API can never be a real no-op? But I'm hoping it might still be much light-weight than TextWriter.Null, if that's ever possible.
Ahhh why is PrintfImpl internal??
https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/printf.fs
Update: My attempts so far
https://sharplab.io/#gist:70dd6f915ba14f36c122f601732af1c1
LevelLogger0 doesn't type check, and my understanding is that both LevelLogger1 and 2 would waste CPU & RAM in logger.Trace "%A" (Array.zeroCreate<double> 1_000_000_000) even when the current threshold is INFO.

I tried the same a time ago and did not find a solution.
Meanwhile string interpolation was added and I find it better at all ends over F#s safe but (as we see here a bit tricky) printf. String interpolation is type safe, improves readability and leads to less brackets. Another disadvantage of printf is its behavior during debugging.
I do logging with an extension method that has the Conditional attribute
type ILogger with // this is Serilog.ILogger which I use
[<Conditional("LOG")>]
member o.InformationConditional(s: string) =
o.Information(s)
to be used like
let a = 72
let b = "hello"
logger.InformationConditional($"foo {a} bar {b}")
Keeping with printf you can create the string with sprintf.

The easiest way to get something like your Printf.noop is to use Printf.kprintf, which takes a continuation that is called with the composed string. If you pass ignore as the continuation, this does nothing. However, this unfortunately does not quite work:
member x.Info (fmt: PrintfFormat<'Printer, _, _, _>): 'Printer =
if level <= LogLevel.INFO then x.Log fmt
else Printf.kprintf ignore fmt
This would logically do the trick, but the issue is that the type of fmt expected by kprintf and fprintf differs (in that they have a different state). So, if you want to do this, you will always need to use kprintf and then decide what to do based on the log level inside the continuation:
member x.Info (fmt: PrintfFormat<'Printer, _, _, _>): 'Printer =
fmt |> Printf.kprintf (fun s ->
if level <= LogLevel.INFO then x.Log "%s" s)

Related

F# kprintf: missing warning about about redundant arguments

Is there a way to do string formatting for Exceptions with kprintf (or similar) and still get warnings about redundant arguments? So far I have:
type MyException (s:string) =
inherit System.Exception(s)
static member Raise msg =
Printf.kprintf (fun s -> raise (MyException(s))) msg
do
MyException.Raise "boom %d" 9 1 // Gives NO warning about redundant arguments
failwithf "boom %d" 9 1 // Gives warning about redundant arguments
I know I could use the new $ formatting but would like to stay compatible with older versions of F#.
The problem does not come from kprintf, but from raise, which allows the return type of the function to be any type, including a curried function. The function failwithf has this warning as a special case.
If you return, let’s say, a string or a unit, you would actually get an error. The downside is that now you cannot raise in every position anymore, as then the return argument is not generic anymore.
You could fix that by forcing a type argument, but that would make it less than ideal for the caller.
For instance, this works, but if you change the return type of f to be a string, it fails:
open System
type MyException (s:string) =
inherit System.Exception(s)
static member Raise msg =
Printf.kprintf (fun s -> raise (MyException s) |> ignore) msg
module Test =
let f() : unit = // string won’t work
MyException.Raise "boom %d" 9 10 // error, as expected
Edit: workaround
Here's some kind of a workaround. You can use generic type constraints to limit the type, and use the fact that an F# function type is not comparable or equatable, does not have a proper value null.
Most types you'll use are equatable, because of F#'s powerful structural equality support. Other options for a type constraint are struct (only structs), null (this excludes record and DU types) or a certain inheritance constraint if that serves your use case.
Example:
type MyException (s:string) =
inherit System.Exception(s)
static member Raise<'T when 'T : equality> msg =
let raiser s =
raise (MyException s)
Unchecked.defaultof<'T> // fool the compiler
Printf.kprintf raiser msg
module Test =
let f() = MyException.Raise "boom %d" 9
let g() = MyException.Raise "boom %d" 9 10 // error
Downside, apart from the type restriction, is that the error you get will be a type error, not a warning as you requested.
The check for this warning in the compiler specifically looks for a number of known F# library functions. You can see the checks in the compiler source code and here (the functions are raise, failwith, failwithf, nullArg, invalidOp and invalidArg).
The reason for this is that, there is, in principle, nothing wrong with a generic function that returns 'T and is used so that 'T is inferred to be a function. For example, it is perfectly fine to do:
let id f = f
id sin 3.14
Here, we define a function id with one argument, but if I give it a function as an argument, it also becomes valid to call it with an extra argument as id sin 3.14.
The special functions are special in that they look like they return a value of a generic type, but they never actually return, because they raise an execption. The compiler cannot, in general, know whether that is the case for any custom function or method you yourself write - and so it only checks this for known special functions. (It would have to do more sophisticated analysis, possibly marking the return type of these functions as the bottom type, but that is beyond what F# does...).

Slow conversion of F# discriminated union case to string

I have around 100k discriminated union cases I have to convert to strings, but it seems to be extremely slow.
As a comparison, the following executes (in F# interactive) in 3seconds on average :
open System
let buf = Text.StringBuilder()
let s = DateTime.Now
for i in 1 .. 100000 do
Printf.bprintf buf "%A" "OtherFinancingInterest" //string
buf.Length <- 0
printfn "elapsed : %0.2f" (DateTime.Now - s).TotalMilliseconds
While the following executes (also in F# interactive) in over a minute...
open System
let buf = Text.StringBuilder()
let s = DateTime.Now
for i in 1 .. 100000 do
Printf.bprintf buf "%A" OtherFinancingInterest //DU
buf.Length <- 0
printfn "elapsed : %0.2f" (DateTime.Now - s).TotalMilliseconds
The discriminated union has 25 values (the result is still extremely slow, around 16 seconds with two cases, but less so than with 25). Any idea if that is "normal" or if I may be doing something wrong ?
Many thanks
The %A format specifier pretty prints any F# value. It uses reflection to do so. It should only really be used for debugging purposes, and not in normal application code.
Note that using %s in your first example using a string makes it a lot faster because there is no type checking needed at runtime.
For the DU, there is a hack you could use to make the reflection only happen once on application load:
type FinancingInterest =
| OtherFinancingInterest
open FSharp.Reflection
let private OtherFinancingInterestStringMap =
FSharpType.GetUnionCases typeof<FinancingInterest>
|> Array.map (fun c -> FSharpValue.MakeUnion(c, [||]) :?> FinancingInterest)
|> Array.map (fun x -> x, sprintf "%A" x)
|> Map.ofArray
type FinancingInterest with
member this.AsString = OtherFinancingInterestStringMap |> Map.find this
You would also use this with the %s format specifier:
Printf.bprintf buf "%s" OtherFinancingInterest.AsString
I had similar timings to yours in your example, and now this one comes down to 40ms.
This only works as long as all of the DU cases don't have an arguments. You will get an exception on application load as soon as you try anything like this:
type FinancingInterest =
| Foo of string
| OtherFinancingInterest
Having said all this, I think you're better off writing a simple function that explicitly converts your type into a string value, writing out the names in full with repetition if necessary. The names of discriminated union cases should not generally be thought of as data that affects your program. You would usually expect to be able to safely rename case names without affecting runtime behaviour at all.

F# Seq.choose() Error FS0001

I have tried MSDN's example for the Seq.choose function (written below) in both a .fsx file and the interactive window for Visual Studio, but it repeatedly returns an error FS0001, stating that the "None" option is a PageExt type rather than the abstract option type 'a option.
I have searched in vain for an explanation of the PageExt type or why this could be returning an error when the None keyword should just represent the "no value" option in the match expression.
let numbers = seq {1..20}
let evens = Seq.choose(fun x ->
match x with
| x when x%2=0 -> Some(x)
| _ -> None ) numbers
printfn "numbers = %A\n" numbers
printfn "evens = %A" evens
;;
| _ -> None ) numbers
---------------------------------------^^^^
>
C:Path\stdin(38,40): error FS0001: This expression was expected to have type
'a option
but here has type
PageExt
Thanks for any help anyone can offer!
The PageExt type is likely something that you've pulled into your current FSI session previously which bound something to None, essentially blocking FSI from recognizing the normal option types.
In F#, you can reuse names, which "shadows" the original value. For example, in FSI, if you type:
let a = 1;;
let a = 2.3;;
a;;
You'll notice that it shows:
val a : int = 1
Then
val a : float = 2.3
Finally
val it : float = 2.3
This isn't changing the definition of a, but rather defining a new a name that shadows (or "hides") the original bound value.
In your case, you have a None name that's bound to something with a PageExt type that's shadowing Option.None, preventing it from being usable.
The easiest way to fix this is to reset your FSI session. Right click in the F# Interactive window, and choose "Reset iteractive session". If you do that, then run the code you pasted, it will work fine.

Delegate/Func conversion and misleading compiler error message

I thought that conversions between F# functions and System.Func had to be done manually, but there appears to be a case where the compiler (sometimes) does it for you. And when it goes wrong the error message isn't accurate:
module Foo =
let dict = new System.Collections.Generic.Dictionary<string, System.Func<obj,obj>>()
let f (x:obj) = x
do
// Question 1: why does this compile without explicit type conversion?
dict.["foo"] <- fun (x:obj) -> x
// Question 2: given that the above line compiles, why does this fail?
dict.["bar"] <- f
The last line fails to compile, and the error is:
This expression was expected to have type
System.Func<obj,obj>
but here has type
'a -> obj
Clearly the function f doesn't have a signature of 'a > obj. If the F# 3.1 compiler is happy with the first dictionary assignment, then why not the second?
The part of the spec that should explain this is 8.13.7 Type Directed Conversions at Member Invocations. In short, when invoking a member, an automatic conversion from an F# function to a delegate will be applied. Unfortunately, the spec is a bit unclear; from the wording it seems that this conversion might apply to any function expression, but in practice it only appears to apply to anonymous function expressions.
The spec is also a bit out of date; in F# 3.0 type directed conversions also enable a conversion to a System.Linq.Expressions.Expression<SomeDelegateType>.
EDIT
In looking at some past correspondence with the F# team, I think I've tracked down how a conversion could get applied to a non-syntactic function expression. I'll include it here for completeness, but it's a bit of a strange corner case, so for most purposes you should probably consider the rule to be that only syntactic functions will have the type directed conversion applied.
The exception is that overload resolution can result in converting an arbitrary expression of function type; this is partly explained by section 14.4 Method Application Resolution, although it's pretty dense and still not entirely clear. Basically, the argument expressions are only elaborated when there are multiple overloads; when there's just a single candidate method, the argument types are asserted against the unelaborated arguments (note: it's not obvious that this should actually matter in terms of whether the conversion is applicable, but it does matter empirically). Here's an example demonstrating this exception:
type T =
static member M(i:int) = "first overload"
static member M(f:System.Func<int,int>) = "second overload"
let f i = i + 1
T.M f |> printfn "%s"
EDIT: This answer explains only the mysterious promotion to 'a -> obj. #kvb points out that replacing obj with int in OPs example still doesn't work, so that promotion is in itself insufficient explanation for the observed behaviour.
To increase flexibility, the F# type elaborator may under certain conditions promote a named function from f : SomeType -> OtherType to f<'a where 'a :> SomeType> : 'a -> OtherType. This is to reduce the need for upcasts. (See spec. 14.4.2.)
Question 2 first:
dict["bar"] <- f (* Why does this fail? *)
Because f is a "named function", its type is promoted from f : obj -> obj following sec. 14.4.2 to the seemingly less restrictive f<'a where 'a :> obj> : 'a -> obj. But this type is incompatible with System.Func<obj, obj>.
Question 1:
dict["foo"] <- fun (x:obj) -> x (* Why doesn't this, then? *)
This is fine because the anonymous function is not named, and so sec. 14.4.2 does not apply. The type is never promoted from obj -> obj and so fits.
We can observe the interpreter exhibit behaviour following 14.4.2:
> let f = id : obj -> obj
val f : (obj -> obj) (* Ok, f has type obj -> obj *)
> f
val it : ('a -> obj) = <fun:it#135-31> (* f promoted when used. *)
(The interpreter doesn't output constraints to obj.)

Type of printfn in F#, static vs dynamic string

I just began toying around with F# in Mono and the following problem arose that I cannot quite understand. Looking up information on printfn and TextWriterFormat didn't bring enlightenment either, so I thought I'm going to ask here.
In FSI I run the following:
> "hello";;
val it : string = "hello"
> printfn "hello";;
hello
val it : unit = ()
Just a normal string and printing it. Fine. Now I wanted to declare a variable to contain that same string and print it as well:
> let v = "hello" in printfn v ;;
let v = "hello" in printfn v ;;
---------------------------^
\...\stdin(22,28): error FS0001: The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>'
I understood from my reading that printfn requires a constant string. I also understand that I can get around this problem with something like printfn "%s" v.
However, I'd like to understand what's going on with the typing here. Clearly, "hello" is of type string as well as v is. Why is there a type problem then? Is printfn something special? As I understand it the compiler already performs type-checking on the arguments of the first string, such that printfn "%s" 1 fails.. this could of course not work with dynamic strings, but I assumed that to be a mere convenience from the compiler-side for the static case.
Good question. If you look at the type of printfn, which is Printf.TextWriterFormat<'a> -> 'a, you'll see that the compiler automatically coerces strings into TextWriterFormat objects at compile time, inferring the appropriate type parameter 'a. If you want to use printfn with a dynamic string, you can just perform that conversion yourself:
let s = Printf.TextWriterFormat<unit>("hello")
printfn s
let s' = Printf.TextWriterFormat<int -> unit>("Here's an integer: %i")
printfn s' 10
let s'' = Printf.TextWriterFormat<float -> bool -> unit>("Float: %f; Bool: %b")
printfn s'' 1.0 true
If the string is statically known (as in the above examples), then you can still have the compiler infer the right generic argument to TextWriterFormat rather than calling the constructor:
let (s:Printf.TextWriterFormat<_>) = "hello"
let (s':Printf.TextWriterFormat<_>) = "Here's an integer: %i"
let (s'':Printf.TextWriterFormat<_>) = "Float: %f; Bool: %b"
If the string is truly dynamic (e.g. it's read from a file), then you'll need to explicitly use the type parameters and call the constructor as I did in the previous examples.
This is only somewhat related to your question, but I think it's a handy trick. In C#, I often have template strings for use with String.Format stored as constants, as it makes for cleaner code:
String.Format(SomeConstant, arg1, arg2, arg3)
Instead of...
String.Format("Some {0} really long {1} and distracting template that uglifies my code {2}...", arg1, arg2, arg3)
But since the printf family of methods insist on literal strings instead of values, I initially thought that I couldn't use this approach in F# if I wanted to use printf. But then I realized that F# has something better - partial function application.
let formatFunction = sprintf "Some %s really long %i template %i"
That just created a function that takes a string and two integers as input, and returns a string. That is to say, string -> int -> int -> string. It's even better than a constant String.Format template, because it's a strongly-typed method that lets me re-use the template without including it inline.
let foo = formatFunction "test" 3 5
The more I use F#, the more uses I discover for partial function application. Great stuff.
I don't think that it is correct to say that the literal value "hello" is of type String when used in the context of printfn "hello". In this context the compiler infers the type of the literal value as Printf.TextWriterFormat<unit>.
At first it seemed strange to me that a literal string value would have a different inferred type depending on the context of where it was used, but of course we are used to this when dealing with numeric literals, which may represent integers, decimals, floats, etc., depending on where they appear.
If you want to declare the variable in advance of using it via printfn, you can declare it with an explicit type...
let v = "hello" : Printf.TextWriterFormat<unit> in printfn v
...or you can use the constructor for Printf.TextWriterFormat to convert a normal String value to the necessary type...
let s = "foo" ;;
let v = new Printf.TextWriterFormat<unit>(s) in printfn v ;;
As you correctly observe, the printfn function takes a "Printf.TextWriterFormat<'a>" not a string. The compiler knows how to convert between a constant string and a "Printf.TextWriterFormat<'a>", but not between a dynamic string and a "Printf.TextWriterFormat<'a>".
This begs the question why can't it convert between a dynamic string and a "Printf.TextWriterFormat<'a>". This is because the compiler must look at the contents of the string to and determine what control characters are in it ( i.e. %s %i etc), from this is it works out the type of the type parameter of "Printf.TextWriterFormat<'a>" (i.e. the 'a bit). This is a function that is returned by the printfn function and means that the other parameters accepted by printfn are now strongly typed.
To make this a little clear in your example "printfn "%s"" the "%s" is converted into a "Printf.TextWriterFormat unit>", meaning the type of "printfn "%s"" is string -> unit.

Resources