F#currying效率?

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#的简洁性和可读性,同时仍然可以获得预先评估集合结构的更有效的行为.

以上只是一个例子 ; 我正在寻找一种避免让我陷入实施细节的一般方法 - 在可能的情况下,我希望避免因实施执行顺序等细节而分心,因为这对我来说通常并不重要,并且会破坏一个主要的卖点函数式编程.

Seb*_*ich 9

只要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.


Bri*_*ian 5

@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%的表达式的影响.这意味着程序员仍然负责如上所述明确编码评估顺序.


Tom*_*cek 5

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)

以下是一些有关计算表达式的更多信息的资源: