Should I see the difference if I do not specify the unit parameter in this bind () argument?

In this F # code, from the section of calculation expressions in the F Sharp Programming Wikibook :

let addThreeNumbers() = let bind(input, rest) = match System.Int32.TryParse(input()) with | (true, n) when n >= 0 && n <= 100 -> rest(n) | _ -> None let createMsg msg = fun () -> printf "%s" msg; System.Console.ReadLine() bind(createMsg "#1: ", fun x -> bind(createMsg "#2: ", fun y -> bind(createMsg "#3: ", fun z -> Some(x + y + z) ) ) ) 

When I convert input() to input and create Msg msg from fun () -> printf "%s" msg; System.Console.ReadLine() fun () -> printf "%s" msg; System.Console.ReadLine() in printf "%s" msg; System.Console.ReadLine() printf "%s" msg; System.Console.ReadLine() :

 let addThreeNumbers() = let bind(input, rest) = match System.Int32.TryParse(input) with | (true, n) when n >= 0 && n <= 100 -> rest(n) | _ -> None let createMsg msg = printf "%s" msg; System.Console.ReadLine() bind(createMsg "#1: ", fun x -> bind(createMsg "#2: ", fun y -> bind(createMsg "#3: ", fun z -> Some(x + y + z) ) ) ) 

the program seems to behave exactly the same when I run it on dotnetfiddle.net. Is this just a marginal case where the unit parameter is not really needed to delay the calculations, since they depend on user input from Console.ReadLine (), is the modified version incorrect or behaves differently, so I didn’t notice ?

+5
source share
1 answer

On practice

You're right. In this case, the fact that the input calculation is “delayed” is completely redundant, because it unconditionally “is not delayed” in the first bind line, so there is no possible scenario in which the delayed calculation is performed later or not at all.

One subtle difference (which does not matter in practice) is this: in the source code Console.ReadLine is called from bind , but in your modified code Console.ReadLine is called before bind and its result is then passed to bind .

If bind was something more complex (say, if it had a try .. with block around input() or something like that) then this difference would make a difference. However, be that as it may, postponement does not add anything.


But theoretically

Another way you could see the difference is that you prepare the read actions in advance "in advance", and not create them in place:

 let msg1 = createMsg "#1: " let msg2 = createMsg "#2: " let msg3 = createMsg "#3: " bind(msg1, fun x -> bind(msg2, fun y -> bind(msg3, fun z -> Some(x + y + z) ) ) ) 

With this code, the original bind will work fine, but your modified bind will cause all three inputs to be executed every time, rather than stopping at the first invalid input.

Although it looks random on the surface, it actually illustrates an important consideration in program design: the difference between evaluation and execution. In terms of "assessment" can be understood as "readiness for work", and "execution" can be understood as "actually doing work." In my snippet above, the line let msg1 = represents the evaluation of the I / let msg1 = action, and the call to bind(msg1, ...) represents the execution of this action.

Understanding this difference can lead to better program design. For example, when an assessment is guaranteed to be separated from execution, it can be optimized or cached or instrumented, etc. Without changing the value of the program. In languages ​​such as Haskell, where the language design itself guarantees the separation of evaluation and execution, the compiler gets unprecedented freedom for optimization, which leads to much faster binary code.

Until I read the book you are talking about, I would suggest that the purpose of this example is to demonstrate this difference, so that although there is no practical point in delayed computing, there may be an educational one.

+7
source

Source: https://habr.com/ru/post/1276272/


All Articles