在这个来自F Sharp Programming Wikibook的计算表达式部分的 F#代码中:
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) ) ) )
Run Code Online (Sandbox Code Playgroud)
当我转换input()到input和create Msg msg来自fun () -> 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) ) ) )
Run Code Online (Sandbox Code Playgroud)
当我在dotnetfiddle.net上运行它时,程序似乎表现完全一样.这只是一个边缘情况,其中实际上不需要单位参数来延迟计算,因为它们依赖于来自Console.ReadLine()的用户输入,或者修改后的版本不正确,或者它在其他方面表现不同还没有注意到?
你是对的.在这种情况下,input计算"延迟" 的事实是完全多余的,因为它在第一行上无条件地"未延迟" bind,因此没有可能的情况,延迟计算将在以后运行或不在所有.
一个微妙的区别(在实践中无关紧要)是:在原始代码中,Console.ReadLine从内部调用bind,但在修改后的代码中,之前Console.ReadLine调用,然后将其结果传递给. bindbind
如果bind某种程度上更复杂(例如,如果它有一个try .. with块input()或类似的东西),那么这种差异将是重要的.然而,事实上,推迟不会增加任何东西.
另一种可以看到差异的方法是,如果您"提前"准备阅读操作,而不是在现场创建它们:
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) ) ) )
Run Code Online (Sandbox Code Playgroud)
使用此代码,原始文件bind可以正常工作,但是您的修改bind将导致所有三个输入每次都发生,而不是停止在第一个无效输入上.
虽然表面看起来是随机的,但它实际上说明了程序设计中的一个重要考虑因素:评估和执行之间的差异.简而言之,"评估"可以理解为"为工作做准备",而"执行"可以理解为"实际做工作".在上面的代码片段中,该行let msg1 =表示对输入读取操作的评估,而调用bind(msg1, ...)表示执行该操作.
很好地理解这种差异可以带来更好的程序设计.例如,当保证评估与执行分开时,可以优化,缓存或检测等,而不改变程序的含义.在诸如Haskell之类的语言中,语言的设计保证了评估和执行的分离,编译器获得了前所未有的优化自由,从而产生了更快的二进制代码.
虽然我没有读过你所指的那本书,但我猜这个例子的目的是为了证明这种差异,因此,虽然推迟计算没有实际意义,但可能会有一个教育性的.
| 归档时间: |
|
| 查看次数: |
48 次 |
| 最近记录: |