How to pass a printf-style function to another function in F#

前端 未结 3 736
臣服心动
臣服心动 2021-01-14 08:36

I\'d like to make a function in F# that accepts a printf-style function as an argument, and uses that argument to output data. Usage would be something like the following:

相关标签:
3条回答
  • 2021-01-14 08:54

    I think the answer by kvb is a great explanation of the problem - why is it difficult to pass printf like functions to other functions as parameter. While kvb gives a workaround that makes this possible, I think it is probably not very practical (because the use of interfaces makes it a bit complex).

    So, if you want to parameterize your output, I think it is easier to take System.IO.TextWriter as an argument and then use printf like function that prints the output to the specified TextWriter:

    let OutputStuff printer =
      Printf.fprintfn printer "Hi there!"
      Printf.fprintfn printer "The answer is: %d" 42
    
    OutputStuff System.Console.Out
    

    This way, you can still print to different outputs using the printf style formatting strings, but the code looks a lot simpler (alternatively, you could use Printf.kprintf and specify a printing function that takes string instead of using TextWriter).

    If you want to print to an in-memory string, that's easy too:

    let sb = System.Text.StringBuilder()
    OutputStuff (new System.IO.StringWriter(sb))
    sb.ToString()
    

    In general, TextWriter is a standard .NET abstraction for specifying printing output, so it is probably a good choice.

    0 讨论(0)
  • 2021-01-14 08:59

    The problem is that when you say (output : Printf.TextWriterFormat<'a> -> 'a), that means "there is some 'a for which output takes a Printf.TextWriterFormat<'a> to an 'a". Instead, what you want to say is "for all 'a output can take a Printf.TextWriterFormat<'a> and return a 'a.

    This is a bit ugly to express in F#, but the way to do it is with a type with a generic method:

    type IPrinter =
        abstract Print : Printf.TextWriterFormat<'a> -> 'a
    
    let OutputStuff (output : IPrinter) =
        output.Print "Header"
        output.Print "Data: %d" 42
    
    OutputStuff { new IPrinter with member this.Print(s) = printfn s }
    
    0 讨论(0)
  • 2021-01-14 09:08

    Using the inline feature of FSharp you can pre-configure the Printf.ksprintf function with a "work" function and thereby have a final function that accepts a format string together with its peculiar required parameters similar to printf or sprintf. The work function, which accepts that resulting string, can presumably do whatever it wants such as in the case of this logging example which prints the string to the console:

    let logger = fun (msg:string) -> System.Console.WriteLine msg
    let inline log msg = Printf.ksprintf logger msg
    

    Usage examples:

    open System
    open System.Globalization.CultureInfo.CurrentCulture
    
    log "Hello %s, how is your %s" name Calendar.GetDayOfWeek(DateTime.Today)
    
    log "132 + 6451 = %d" (132+6451)
    
    ...
    
    0 讨论(0)
提交回复
热议问题