简体   繁体   中英

How to wrap printfn in F#?

I have read about continuations and partial applications; I am also aware of the kprintf function.

But I still don't know how to write something like:

let myPrintFunction format variable_length_arguments_list =
    let a = sprintf format variable_length_ argument_list
    do other things

what would be the syntax this this?

so I could use it like:

myPrintFunction "%s : %i" "hello" 3

Edit:

This is different than How do I implement a method with a variable number of arguments? because that question is asking how to make a method with a variable number of arguments, but the issue I am facing is to pass that variable number of argument to the next function (sprintf) that takes a variable number of arguments too. Or, at least that's where I suppose the problem is.

The test code, based on the solution proposed by Scott can be found here: https://dotnetfiddle.net/oCzcS9

I want to demonstrate the ksprintf function, because that one accepts a continuation that will allow you to pass on the resulting string to eg a log system.

For the purpose of demonstration, let's first create something that can take a single string as input and pass it on, in this case to the console.

let writeStringToConsole (s: string) = Console.WriteLine ("OUTPUT : " + s)

So now, if writeStringToConsole is all we have, how to we make it accept F# formatting?

let printToConsole format = Printf.ksprintf writeStringToConsole format

Example that demonstrates that it works.

type DU = A | B
let i = 7
let s = "thirteen"
let du = B

printToConsole """an int %i and a string "%s" here""" i s
printToConsole """an int %i and a string "%s" and DU %A here""" i s du

// OUTPUT : an int 7 and a string "thirteen" here
// OUTPUT : an int 7 and a string "thirteen" and DU B here

// Note that OUTPUT is also part of the actual output,
// and it demonstrates how you can add e.g. a timestamp
// or line number or something to the output string, without
// it being part of the formatting.

edit: Some additional notes

The format string must be a literal. That's because the literal string must be read at compile time in order to compute the function that must be returned in order to gobble up whatever values/types that follow the format string.

For example, if you do printToConsole "%i %s %A %A" 7 "x" myType yourType , then you'll see int -> string -> MyType -> YourType in the signature of printToConsole where it's used.

There is a way to use plain strings as format strings with this system, but I don't remember how it's done, and anyway it spoils the type safety. It comes in handy when doing internationalization of strings, and your format strings must come from a resource and not F# source due to external translator services.

edit 2: Wrap eg log system

I created an interface to use for various logging systems, which pretty much share the same features.

type ILogger =

...

    abstract member Debugf: StringFormat<'h, unit> -> 'h
    abstract member Verbosef: StringFormat<'h, unit> -> 'h
    abstract member Infof: StringFormat<'h, unit> -> 'h
    abstract member Warningf: StringFormat<'h, unit> -> 'h
    abstract member Errorf: StringFormat<'h, unit> -> 'h
    abstract member Fatalf: StringFormat<'h, unit> -> 'h

Then an implementation for my currently used logging system looks like this.

type internal SiLogger(session: Session) =
    let slogf = Printf.ksprintf

...

    interface ILogger with

...

    member _.Debugf format = slogf session.LogDebug format
    member _.Verbosef format = slogf session.LogVerbose format
    member _.Infof format = slogf session.LogMessage format
    member _.Warningf format = slogf session.LogWarning format
    member _.Errorf format = slogf session.LogError format
    member _.Fatalf format = slogf session.LogFatal format

And there is a null logger.

let slogf = Printf.ksprintf

let dummyLog _ = () // The parameter is the title string.

let dummy format = slogf dummyLog format

let getNullLogger () =
    { new ILogger with

...

        member _.Debugf format = dummy format
        member _.Verbosef format = dummy format
        member _.Infof format = dummy format
        member _.Warningf format = dummy format
        member _.Errorf format = dummy format
        member _.Fatalf format = dummy format

...

        }
open System

let myPrintFunction (format: Printf.StringFormat<_>) ([<ParamArray>] args) =
    let a = sprintf format args
    a

myPrintFunction "%s : %i" "hello" 3

To add the PrintF as a member function, this is the closest I could get. As you see, I had to pass the format string separately (in the constructor, or I could have used a property setter). I could find no way to pass the format string as the first parameter of the PrintF function as I could for a free function (see my other answer at https://stackoverflow.com/a/58822618/5652483 ).

Also, if I uncomment the line this.RaiseSomeEvent msg , then it breaks. So I could find no way to enable the PrintF function to have a side effect.

Hopefully, someone else can solve these issues.

type Foo (format: Printf.StringFormat<_>) =
    member this.RaiseSomeEvent msg = printf "%s" msg

    member this.PrintF ([<ParamArray>] args) =
        let msg = sprintf format args
        //this.RaiseSomeEvent msg
        msg

let foo = Foo("%s : %i")
foo.PrintF "hello" 3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM