【问题标题】:currying multiple functions in parallel in F#在 F# 中并行柯里化多个函数
【发布时间】:2019-01-25 22:21:11
【问题描述】:

我目前正在尝试学习 F#,但遇到了一个我无法解决且在 google 上找不到任何答案的问题。

最初我想要一个日志函数,它可以像 printf 系列函数一样工作,我可以提供一个格式字符串和一些参数(静态检查),但它会在打印出来之前添加一些元数据。通过谷歌搜索,我发现使用如下函数可以做到这一点:

let LogToConsole level (format:Printf.TextWriterFormat<'T>) =
    let extendedFormat = (Printf.TextWriterFormat<string->string->'T> ("%s %s: " + format.Value))
    let date = DateTime.UtcNow.ToString "yyyy-MM-dd HH:mm:ss.fff"
    let lvl = string level
    printfn extendedFormat date lvl

printfn 函数作为该函数的最后一行允许 printf 语法的类似 varargs 的魔力,从而返回部分应用的 printfn 方法以允许调用者完成应用参数。

但是,如果我有多个具有相同签名的此类函数,例如 LogToConsoleLogToFile 和其他函数,我怎么能编写一个函数来调用它们来保持这种部分应用程序的魔力?

基本我正在寻找如何实现函数MultiLog 这将允许我从单个函数调用中调用多个类似 printf 的函数,例如在下面的 ResultIWant 函数中:

type LogFunction<'T> = LogLevel -> Printf.TextWriterFormat<'T> -> 'T

let MultiLog<'T> (loggers:LogFunction<'T>[]) level (format:Printf.TextWriterFormat<'T>) :'T = 
    loggers
    |> Seq.map (fun f -> f level format)
    |> ?????????

let TheResultIWant =
    let MyLog = MultiLog [LogToConsole; LogToFile]
    MyLog INFO "Text written to %i outputs" 2


也许这个问题的本质可以更简洁地抓住:给定一个具有相同签名的函数列表,我如何才能部分地使用相同的参数应用它们?

type ThreeArg = string -> int -> bool -> unit
let funcs: ThreeArg seq = [func1; func2; func3]

let MagicFunction = ?????

// I'd like this to be valid
let partiallyApplied = MagicFunction funcs "string"

// I'd also like this to be valid
let partiallyApplied = MagicFunction funcs "string" 255

// and this (fullyApplied will be `unit`)
let fullyApplied = MagicFunction funcs "string" 255 true

【问题讨论】:

    标签: functional-programming f#


    【解决方案1】:

    要回答有关字符串格式的问题的特定部分,有一个有用的函数Printf.kprintf,它可以让您以一种非常简单的方式做您需要的事情 - 该函数的第一个参数是一个延续,它被调用格式化字符串作为参数。在此继续中,您可以只获取格式化的字符串并将其写入您想要的所有记录器。这是一个基本示例:

    let Loggers = [printfn "%s"]
    
    let LogEverywhere level format =
        Printf.kprintf (fun s ->
          let date = DateTime.UtcNow.ToString "yyyy-MM-dd HH:mm:ss.fff"
          let lvl = string level
          for logger in Loggers do logger (sprintf "%s %s %s" date lvl s)) format
    
    LogEverywhere "BAD" "hi %d" 42
    

    我认为在更一般的情况下,没有一种简单而简单的方法可以做你想做的事情 - 我怀疑你可能能够使用一些反射或静态成员约束魔法,但幸运的是,你没有'在这种情况下不需要!

    【讨论】:

      【解决方案2】:

      几乎没有什么可以添加到完美的@TomasPetricek 答案中,因为他基本上是 F# 中的“半神”。想到的另一种选择是使用计算表达式(例如,参见:https://fsharpforfunandprofit.com/series/computation-expressions.html)。如果使用得当,它确实看起来很神奇:) 但是,我觉得它对于你描述的问题来说有点太重了。

      【讨论】:

      • 感谢您的回答,但我不确定在这种情况下如何使用计算表达式。你能举个例子吗?我知道它们允许您修改选择的运算符并基本上包装/解包/有条件地执行代码位,但对它们不够熟悉,无法理解如何真正使用它们。
      • 用一个非常简单的术语来说,计算表达式允许将每个动作“包装”到一些标准动作中。我认为参考文献中的一个例子谈到了计算表达式可以用于“记录”操作,而用户实际上不需要做太多事情。在您的情况下,您有多个记录器,因此,在扩展示例之后,用户不会知道应用了许多记录器。这很重,但就像魔术一样工作。在我可以使用它之前,我多次阅读该参考资料:(
      • 啊,是的,我明白了。对于隐式注销绑定,这确实是一个可行的选择。
      猜你喜欢
      • 1970-01-01
      • 2011-02-11
      • 1970-01-01
      • 2016-01-01
      • 1970-01-01
      • 2021-02-07
      • 2012-10-06
      • 1970-01-01
      • 2013-11-16
      相关资源
      最近更新 更多