代码之家  ›  专栏  ›  技术社区  ›  Ashok Bhaskar

在这个bind()参数中省略unit参数时,我应该看到区别吗?

  •  6
  • Ashok Bhaskar  · 技术社区  · 7 年前

    在此F代码中 computation expressions section of 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) ) ) )
    

    当我转换时 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) ) ) )
    

    当我在dotnetfiddle上运行该程序时,它的行为似乎完全相同。网这只是一种边缘情况,实际上不需要单元参数来延迟计算,因为它们依赖于来自控制台的用户输入。ReadLine(),或者修改后的版本是否不正确,或者是否以我没有注意到的方式表现不同?

    1 回复  |  直到 7 年前
        1
  •  7
  •   Fyodor Soikin    7 年前

    在实践中

    你是对的。在这种情况下 input 计算“延迟”是完全多余的,因为它在 bind ,因此不存在延迟计算稍后运行或根本不运行的可能场景。

    一个细微的区别(实际上并不重要)是:在原始代码中, Console.ReadLine 从内部调用 绑定 ,但在修改后的代码中, 安慰读线 被称为 之前 绑定 ,然后将其结果传递到 绑定

    如果 绑定 某种程度上更复杂(比如,如果它有 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) ) ) )
    

    使用此代码,原始 绑定 可以,但您的 绑定 将导致每次都发生所有三个输入,而不是在第一个无效输入时停止。

    虽然表面上看这是随机的,但实际上它说明了程序设计中的一个重要考虑因素: 评价 处决 .简单地说,“评估”可以理解为“为工作做准备”,而“执行”可以理解为“实际做工作”。在我上面的代码片段中 let msg1 = 代表 评价 在调用 bind(msg1, ...) 代表 处决 这一行动。

    很好地理解这种差异可以导致更好的程序设计。例如,当保证评估与执行分离时,可以在不改变程序含义的情况下对其进行优化、缓存或插入指令等。在Haskell这样的语言中,语言的设计保证了计算和执行的分离,编译器获得了前所未有的优化自由,从而产生了更快的二进制代码。

    虽然我没有读过你提到的那本书,但我猜这个例子的目的是为了证明这种差异,因此,虽然推迟计算没有实际意义,但可能有教育意义。