查看FSharp.Core和PowerPack中的源代码,我发现接受具有两个或更多参数的函数的许多高阶函数都使用FSharpFunc.Adapt。例如:
let mapi f (arr: ResizeArray<_>) =
let f = FSharpFunc<_,_,_>.Adapt(f)
let len = length arr
let res = new ResizeArray<_>(len)
for i = 0 to len - 1 do
res.Add(f.Invoke(i, arr.[i]))
res
FSharpFunc.Adapt
上的文档相当薄。这是我们应该在任何时候使用具有类似签名的高阶函数时使用的一般最佳实践吗?只有多次调用传入函数?它的优化程度是多少?我们应该在任何地方使用Adapt
,还是只能很少使用?
感谢您的时间。
答案 0 :(得分:13)
这很有意思!我没有任何官方信息(我没有在任何地方看到这些信息),但这里有一些关于Adapt
函数如何工作的想法。
像mapi
这样的函数采用函数的curried形式,这意味着参数的类型被编译为类似FSharpFunc<int, FSharpFunc<T, R>>
的类型。但是,许多函数实际上直接编译为两个参数的函数,因此实际值通常为FSharpFunc<int, T, R>
,它继承自FSharpFunc<int, FSharpFunc<T, R>>
。
如果你调用这个函数(例如f 1 "a"
),F#编译器会生成如下内容:
FSharpFunc<int, string>.InvokeFast<a>(f, 1, "a");
如果你使用Reflector查看InvokeFast
函数,你会看到它测试函数是否被编译为优化版本(f :? FSharpFunc<int, T, R>
)。如果是,则直接调用Invoke(1, "a")
,如果不是,则需要进行两次调用Invoke(1).Invoke("a")
。
每次调用作为参数传递的函数时都会执行此检查(执行检查然后使用优化调用可能会更快,因为这更常见)。
Adapt
函数的作用是将任何函数转换为FSharpFunc<T1, T2, R>
(如果函数未被优化,它会为它创建一个包装器,但大多数时候情况并非如此)。对自适应函数的调用会更快,因为它们不需要每次都进行动态检查(只在Adapt
内进行一次检查。)
因此,总结是Adapt
可以提高性能,如果您调用作为参数传递的函数,该函数多次使用多于1个参数。与任何优化一样,我不会盲目地使用它,但在调整性能时要注意这一点是一件有趣的事情!
(顺便说一句:感谢一个非常有趣的问题,我不知道编译器会这样做: - )