我在我的F#应用程序中使用log4net,并希望通过创建一些函数来使日志记录更加惯用F#.
对于警告,我创建了以下功能:
let log = log4net.LogManager.GetLogger("foo")
let warn format = Printf.ksprintf log.Warn format
Run Code Online (Sandbox Code Playgroud)
这很好用,我可以这样做:
warn "This is a warning"
// and
warn "The value of my string is %s" someString
Run Code Online (Sandbox Code Playgroud)
但是我想让它更通用,并且可以传入我想要的记录器.因此,我将该函数包装在另一个可以将ILog作为参数的函数中:
let getWarn (logger: log4net.ILog) =
fun format -> Printf.ksprintf logger.Warn format
Run Code Online (Sandbox Code Playgroud)
我也尝试过:
let getWarn (logger: log4net.ILog) format =
Printf.ksprintf logger.Warn format
Run Code Online (Sandbox Code Playgroud)
当我现在使用它时:
let warn = getWarn log4net.LogManager.GetLogger("bar")
warn "This is a warning"
Run Code Online (Sandbox Code Playgroud)
这有效,但是当我这样做时:
warn "The value of my string is %s" someString
Run Code Online (Sandbox Code Playgroud)
我收到一个编译器错误说"The value is not a function and cannot be applied"
如果我删除了第一个warn语句,它就可以了.
所以我想编译器根据我使用的第一个语句推断出类型warn,因此我总是需要以相同的方式使用它.
但是为什么在我的第一个例子中并不是这种情况,我warn直接使用函数而不是通过getWarn?
这是价值限制.
当你声明warn为普通函数时,它只是一个通用函数,没有大惊小怪.但是当你声明它没有参数时,它变成一个值(它是一个函数类型的值,但仍然是一个值;有一个细微的差别),因此受到值限制,简而言之,它表示值不能是通用的.
尝试删除warn(不仅仅是第一个)的所有用法.你会得到编译器抱怨这个:Value restriction. The value 'warn' has been inferred to have generic type.请按照顶部的链接进行更多讨论.
当然这有点太强了,所以F#稍微放宽了这个规则:如果在声明的附近使用了值,编译器将根据用法修复泛型参数.这就是为什么它适用于第一次使用,但不适用于第二次使用.你正确地理解了那部分.
一种解决方法是添加显式参数,从而形成warn"声明的函数",而不是"函数类型的值".试试这个:
let warn() = getWarn log4net.LogManager.GetLogger("bar")
Run Code Online (Sandbox Code Playgroud)
另一种解决方法(实际上是相同的 - 见下文)是显式声明泛型参数,还添加类型注释:
let warn<'a> : StringFormat<'a, unit> -> 'a = log4net.LogManager.GetLogger("bar")
Run Code Online (Sandbox Code Playgroud)
在这种情况下,类型注释是必需的,因为否则编译器不知道泛型参数如何与值类型相关.
这样,您可以使用它,就像它是一个真正的通用值:
warn "This is a warning"
warn "The value of my string is %s" someString
Run Code Online (Sandbox Code Playgroud)
但有一个问题:这个技巧与添加单位参数(上面的解决方法,上面)非常相似.在幕后,编译器会将此定义作为真正声明的泛型函数发出,并为其提供单个unit参数.这个含义(以及以前的解决方法)是每次使用时warn,它被调用-即每次通话时间warn,你叫getWarn和GetLogger了.在这个特定的例子中,这可能没问题,但是如果你在生成返回函数之前做了一些重要的工作,那么每次调用都会重新完成这项工作.
这使我们对您的方法有了更深层次的问题:您试图在不失去其通用性的情况下传递函数.不能这样做.不能有函数值,从函数返回它或将它传递给函数,并且仍然保持通用.试试这个:
let mapTuple f (a,b) = (f a, f b)
let makeList x = [x]
mapTuple makeList (1, "abc")
Run Code Online (Sandbox Code Playgroud)
本能地,人们会期望这可以工作并产生一个列表元组 - ( [1], ["abc"] )对吧?好吧,它不会像这样工作.声明时mapTuple f (a,b),该f参数必须是某种特定类型.它不能是一个开放的泛型类型,因此你可以应用它a,b即使它们是不同的类型.相反,推理作品的另一种方式:因为f是一个类型的,认为编译器,它被应用到a和b,然后a和b必须是同一类型的.因此签名被推断为mapTuple: ('a -> 'b) -> 'a*'a -> 'b*'b.
所以最重要的是,如果你只想制作一个warn函数,只需给它一个参数,你就没事了.但是,如果你想同时生成两个(正如你在评论中提到的那样),那你就不幸了:他们必须最终属于同一类型,你不能把它们称为通用函数.重新制作.
如果你想拥有一个函数来返回一个函数(或多个函数)而不失去它的通用性,你将不得不返回一个接口.接口上的成员函数可以是通用的,与接口本身的通用性无关:
type loggers =
abstract member warn: Printf.StringFormat<'a, unit> -> 'a
abstract member err: Printf.StringFormat<'a, unit> -> 'a
let getLoggers (log: log4net.ILog) =
{ new loggers with
member this.warn format = Printf.ksprintf log.Warn format
member this.err format = Printf.ksprintf log.Error format }
let logs = getLoggers (log4net.LogManager.GetLogger("Log"))
logs.warn "This is a warning"
logs.warn "Something is wrong: %s" someString
Run Code Online (Sandbox Code Playgroud)