简体   繁体   中英

Right understanding about F# pre-computation logic

This question is extended from my previous question , about mutable value. I'm pretty sure that the main topic of this question, pre-computation has many things to do with the linked question.

Please see below examples, which I have brought from the book I'm studying with:

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

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

Which accept an string list, and returns function which checkes whether input string is in the list. With this tiny cute helper function, here are two examples:

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

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

I thought these two function do excatly the same thing, but it was not the case. The book says while first one pre-computes the set from the given list, the second one will compute the set whenever the function has called.

As I learned in PL class, in order to evaluate a lambda calculus expression, every parameter should be given to the body. Lacking only one will not allow an expression to be evaulated.

Based on this, I've concluded that the first one has no parameter, so it can immidiately start evaluating when the list is given, but the second one can't start evaluating until parameter word is given. It's fine until here, but after thinking about it with the above linked question, I've become not sure whether I'm correctly understanding it or not.

Thinking from it and the answer of linked question, it seems like the evaluation continues until it becomes not able to evaluate, possibly because the lack of information, parameters, or anything. Then, is it OK to think that every situation-free part of expression will be evaluated only once and pre-computed, just like the first example?

It seems like this part may heavily affect to optimization and performance, so I want to make my understanding about this topic clear.

I've concluded that the first one has no parameter, so it can immidiately start evaluating when the list is given, but the second one can't start evaluating until parameter word is given.

This is exactly right.

It seems like the evaluation continues until it becomes not able to evaluate, possibly because the lack of information, parameters, or anything.

This is essentially also right, but it is simpler than your formulation make it sound. The "lack of information" is not something very sophisticated - it is simply the fact that lambda functions are values and cannot be evaluated until their parameters are specified.

It may be a bit easier to understand this if we rewrite everything using the fun x ->.. notation:

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

The evaluation proceeds from top to bottom.

  1. The expression assigned to isWord is a function, so the body cannot be evaluated.
  2. The expression assigned to isCapital is a function application, so it can be evaluated. This in turn evaluates the value of wordTable and returns a function - which is a function and cannot be evaluated.
  3. The expression assigned to isCapitalSlow is a function and cannot be evaluated.
  4. If you later call isCapitalSlow "Prague" , this will be a function application and so it can be evaluated. It will then invoke isWord with a list of cities as an argument, which will, in turn, invoke Set.ofList to build wordTable and produce a function which is then evaluated with word as an argument.

Since you seem to be familiar with C#, we can rewrite this as a C# class:

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

What would the equivalent functions look like?

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);

Note that isCapital creates an instance of the class once, and returns its contains method. So every time you call isCapital , you're actually only calling HashSet.Contains .

In isCapitalSlow you're creating an instance of IsWord , and in turn a HashSet every single time you call the method. This would naturally be slower.

In idiomatic F#, you would write this as:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM