Eam*_*nne 9 f# functional-programming currying
我有一个看起来如下的函数:
let isInSet setElems normalize p =
normalize p |> (Set.ofList setElems).Contains
Run Code Online (Sandbox Code Playgroud)
此函数可用于快速检查元素是否在语义上属于某个集合的一部分; 例如,要检查文件路径是否属于html文件:
let getLowerExtension p = (Path.GetExtension p).ToLowerInvariant()
let isHtmlPath = isInSet [".htm"; ".html"; ".xhtml"] getLowerExtension
Run Code Online (Sandbox Code Playgroud)
然而,当我使用如上所述的函数时,性能很差,因为在"isInSet"中写入的函数体的评估似乎被延迟,直到所有参数都已知 - 特别是,诸如(Set.ofList setElems).Contains每次执行时重新评估的不变位isHtmlPath.
我怎样才能最好地保持F#的简洁性和可读性,同时仍然可以获得预先评估集合结构的更有效的行为.
以上只是一个例子 ; 我正在寻找一种避免让我陷入实施细节的一般方法 - 在可能的情况下,我希望避免因实施执行顺序等细节而分心,因为这对我来说通常并不重要,并且会破坏一个主要的卖点函数式编程.
只要F#不区分纯代码和不纯代码,我怀疑我们会看到这种优化.但是,您可以明确说明.
let isInSet setElems =
let set = Set.ofList setElems
fun normalize p -> normalize p |> set.Contains
Run Code Online (Sandbox Code Playgroud)
isHtmlSet现在将isInSet只调用一次以获得闭包,同时执行ofList.
@Kha的答案就是现场.F#无法重写
// effects of g only after both x and y are passed
let f x y =
let xStuff = g x
h xStuff y
Run Code Online (Sandbox Code Playgroud)
成
// effects of g once after x passed, returning new closure waiting on y
let f x =
let xStuff = g x
fun y -> h xStuff y
Run Code Online (Sandbox Code Playgroud)
除非它知道它g没有任何效果,并且在今天的.NET Framework中,通常无法推断99%的表达式的影响.这意味着程序员仍然负责如上所述明确编码评估顺序.
Kha的答案显示了如何通过直接使用闭包来手动优化代码.如果这是您需要经常使用的频繁模式,那么还可以定义一个高阶函数,该函数从两个函数构造有效代码 - 第一个执行某些参数的预处理,第二个执行某些参数的预处理一旦获得剩余的参数,实际处理.
代码如下所示:
let preProcess finit frun preInput =
let preRes = finit preInput
fun input -> frun preRes input
let f : string list -> ((string -> string) * string) -> bool =
preProcess
Set.ofList // Pre-processing of the first argument
(fun elemsSet (normalize, p) -> // Implements the actual work to be
normalize p |> elemsSet.Contains) // .. done once we get the last argument
Run Code Online (Sandbox Code Playgroud)
这是一个更优雅的问题......
另一个(疯狂的)想法是你可以使用计算表达式.允许你这样做的计算构建器的定义非常不标准(它不是人们通常用它们做的事情,它与monad或任何其他理论没有任何关系).但是,应该可以这样写:
type CurryBuilder() =
member x.Bind((), f:'a -> 'b) = f
member x.Return(a) = a
let curry = new CurryBuilder()
Run Code Online (Sandbox Code Playgroud)
在curry计算中,您可以let!用来表示您想要获取函数的下一个参数(在评估前面的代码之后):
let f : string list -> (string -> string) -> string -> bool = curry {
let! elems = ()
let elemsSet = Set.ofList elems
printf "elements converted"
let! normalize = ()
let! p = ()
printf "calling"
return normalize p |> elemsSet.Contains }
let ff = f [ "a"; "b"; "c" ] (fun s -> s.ToLower())
// Prints 'elements converted' here
ff "C"
ff "D"
// Prints 'calling' two times
Run Code Online (Sandbox Code Playgroud)
以下是一些有关计算表达式的更多信息的资源:
在我的书的免费样本章节中描述了使用计算表达式的常用方法:第12章:序列表达式和替代工作流程(PDF)