【问题标题】:Right understanding about F# pre-computation logicF#预计算逻辑的正确理解
【发布时间】:2020-07-21 16:09:52
【问题描述】:

这个问题是从我的previous question 扩展而来的,关于可变值。我很确定这个问题的主题 pre-computation 与链接的问题有很多关系。

请看下面的例子,这些例子来自我正在学习的书中:

let isWord (words : string list) =
    let wordTable = Set.ofList words     // Expensive computation!
    fun w -> wordTable.Contains(w)

val isWord : words:string list -> (string -> bool)

它接受一个字符串列表,并返回检查输入字符串是否在列表中的函数。有了这个小巧可爱的辅助函数,这里有两个例子:

let isCapital = isWord ["London"; "Paris"; "Warsaw"; "Tokyo"];;
val isCapital : (string -> bool)

let isCapitalSlow word = isWord ["London"; "Paris"; "Warsaw"; "Tokyo"] word
val isCapitalSlow : (string -> bool)

我认为这两个函数做的事情完全一样,但事实并非如此。书中说,虽然第一个 预先计算给定列表中的集合,但第二个将在函数调用时计算集合。

正如我在 PL 课中学到的,为了计算 lambda 演算表达式,每个参数都应该被赋予函数体。仅缺少一个将不允许对表达式进行评估。

基于此,我得出结论,第一个没有参数,因此它可以在给出列表时立即开始评估,但第二个在给出参数 word 之前无法开始评估。到这里为止都很好,但是在考虑了上面链接的问题之后,我不确定我是否正确理解它。

从它和相关问题的答案中思考,评估似乎一直持续到它变得无法评估,可能是因为缺乏信息、参数或任何东西。那么,是否可以认为表达式的每个 situation-free 部分将只计算一次并预先计算,就像第一个示例一样?

这部分似乎对优化和性能有很大影响,所以我想澄清一下我对这个主题的理解。

【问题讨论】:

  • 没有检查,似乎 isCapital 是一个值。该值是一个函数,在调用 isWord 时返回。因此,计算 isCapital 时会计算一次 wordTable。但是 isCapitalSlow 不是一个值——它是一个函数,所以每次调用它时都会调用 isWord。
  • 如果还不清楚,请记住函数是 F# 语言的一等成员。 isCapital 是一个值,但该值是一个计算出来的函数。另一方面,isCapitalSlow 是编码(术语?)函数,而不是计算函数,因此不是值。 isWord 是一个计算函数的函数。

标签: functional-programming f#


【解决方案1】:

由于您似乎对 C# 很熟悉,我们可以将其重写为 C# 类:

class IsWord
{
    HashSet<string> set;
    public IsWord(string[] words) => set = new HashSet<string>(words);
    public bool Contains(string word) => set.Contains(word);
}

等效的函数是什么样的?

Func<string, bool> isCapital = 
    new IsWord(new[] { "London", "Paris", "Warsaw", "Tokyo" }).Contains;

Func<string, bool> isCapitalSlow = 
    (word) => new IsWord(new[] { "London", "Paris", "Warsaw", "Tokyo" }).Contains(word);

请注意,isCapital 会创建一次该类的实例,并返回其 contains 方法。所以每次你打电话给isCapital,实际上你只是打电话给HashSet.Contains

isCapitalSlow 中,您正在创建IsWord 的一个实例,然后每次调用该方法时都会创建一个HashSet。这自然会慢一些。

在惯用的 F# 中,您可以这样写:

let isWord words =
    let wordTable = Set.ofList words     
    let contains word = wordTable |> Set.contains word
    contains

【讨论】:

    【解决方案2】:

    我的结论是第一个没有参数,所以它可以在给出列表时立即开始评估,但第二个在给出参数字之前不能开始评估。

    这完全正确。

    评估似乎一直持续到无法评估,可能是因为缺乏信息、参数或任何东西。

    这基本上也是正确的,但它比你的表述听起来更简单。 “缺乏信息”并不是什么很复杂的问题——它只是 lambda 函数是值并且在指定它们的参数之前无法评估的事实。

    如果我们使用fun x -&gt; .. 符号重写所有内容,这可能会更容易理解:

    let isWord = fun (words : string list) =
        let wordTable = Set.ofList words
        fun w -> wordTable.Contains(w)
    
    let isCapital = 
      isWord ["London"; "Paris"; "Warsaw"; "Tokyo"]
    
    let isCapitalSlow = fun word -> 
      isWord ["London"; "Paris"; "Warsaw"; "Tokyo"] word
    

    评估从上到下进行。

    1. 分配给isWord 的表达式是一个函数,因此无法计算主体。
    2. 分配给isCapital 的表达式是一个函数应用程序,因此可以对其求值。这反过来会计算 wordTable 的值并返回一个函数 - 这是一个函数,不能被计算。
    3. 分配给isCapitalSlow 的表达式是一个函数,无法计算。
    4. 如果您稍后调用isCapitalSlow "Prague",这将是一个函数应用程序,因此可以对其进行评估。然后它将调用 isWord 并使用城市列表作为参数,然后调用 Set.ofList 来构建 wordTable 并生成一个函数,然后使用 word 作为参数对其进行评估。

    【讨论】:

    • 每个 F# something 都是一个值,没有任何参数就无法评估。嗯,就是这么简单。感谢您详细的回答!
    • 澄清一点——每个 F# 都是一个表达式,包括fun x -&gt; x + 1x + 11。其中一些表达式是值,无法计算,例如1fun x -&gt; x + 1。有些不是值,可以进行评估,例如 1 + 2(在 lambda 演算中,您会在应用函数时通过替换得到它)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多