【问题标题】:What's the value of currying in Functional Programming?函数式编程中柯里化的价值是什么?
【发布时间】:2011-09-29 09:49:13
【问题描述】:

我知道柯里化的概念和使用方法,但我想知道它在实践中的价值是什么?

【问题讨论】:

标签: functional-programming currying


【解决方案1】:

正如相关问题所涵盖的 Practical use of curried functions? ,人们重视并使用柯里化的原因有很多,包括:

  • 改进代码重用——特殊情况函数只是部分应用(和柯里化)的泛型函数
  • 提高代码可读性 -- map (+2)map (\x -> x + 2) 更容易阅读
  • 改进的性能 -- 柯里化可以产生明显的某些特化,一个好的编译器会为你生成特化的版本
  • 有趣——更简单的代码,更漂亮的代码让生活更愉快。

【讨论】:

    【解决方案2】:

    我发现的真正好处:

    • 更少的错误 - 与命令式控制流相比,通过函数组合来组合代码往往会产生更正确的代码。例如,如果您使用“map”而不是“for 循环”,则可以消除许多“off by one”索引错误的风险

    • 更好的并发性 - 使用纯、无副作用函数创建的代码自动是线程安全的。将其与不可变的持久数据结构结合起来,您就有了编写健壮并发代码的好方法。 Clojure 对此特别有用 - 请参阅 http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

    • 更简洁和易于管理的代码 - 在我完全不科学的分析中,函数式代码似乎比命令式 OOP 代码短得多。随着程序变得越来越大,这种好处往往会更加明显。我认为这有几个原因:

      • 函数式语言的语法往往非常简洁。例如,Haskell 和各种 Lisps 都有非常简单和优雅的语法
      • 函数式语言倾向于鼓励组合(高阶函数),而不是将程序的各个部分“粘合”在一起。因此,您避免了许多其他范例中固有的大量样板。
      • 关于组合点,我发现在函数式语言中应用 DRY 原则更容易。如果你看到一个常见的模式,几乎总是可以很容易地将它提取到一个高阶函数(或者如果你是一个 Lisper 的宏)中,这样你就可以在其他地方使用它。
    • 可测试性 - 当您主要使用纯函数编写代码时,编写健壮的测试非常容易。

    当然有一些缺点可以抵消这一点:

    • 写起来更难 - 你需要更多的思维敏捷性,因为你需要在脑海中掌握相当复杂的抽象概念。如果您是受过训练的数学家(我是),这会有所帮助,但我仍然发现编写函数式代码比 OOP 更难。
    • 存在一些性能开销 - 函数式语言使用各种必然意味着一定程度的开销的结构。虽然这可以通过好的编译器变得非常小,但它永远无法完全消除。
    • 库/工具支持 - 这几乎完全是由于 OOP 平台和工具更加成熟,但这仍然是一个问题。从长远来看,这不会成为问题,但我发现的最佳解决方案是使用 Clojure,它可以从大多数 Java 平台库和工具中受益。

    【讨论】:

    • 啊,我误读了这个问题,回答的更像是“函数式编程的价值是什么”。哎呀!
    【解决方案3】:

    我会说有点像Once and Only Once

    我发现自己在 C/C++ 中编写代码,例如

    configure_grid (grid, first_column, last_column, action) {
        for (i = first_column; i <= last_column; ++i)
            // ...
    }
    
    configure_grids (action) {
       congifure_grid (alpha, first_alpha, last_alpha, action);
       congifure_grid (beta, first_beta, last_beta, action);
    }
    

    而不是为每个 alpha 和 beta 编写一次 for 循环。这类似于程序代码中的柯里化。这里的优势很明显。

    柯里化是一个重要的理论概念,但在实用方面,这是优势。

    其实我记得曾经用C写过一个测试套件,有点像这样:

    typedef bool (*predicate) (const type *);
    
    const char * argument;
    
    bool do_foo (const type * t) {
        return bar (t, argument);
    }
    
    bool do_baz (const type * t) {
        return bap (t, argument);
    }
    
    predicate foo (const char * arg) {
        argument = arg;
        return do_foo;
    }
    
    predicate baz (const char * arg) {
        argument = arg;
        return do_baz;
    }
    
    assert (for_all (data_set("alpha"), foo ("abc")));
    assert (for_all (data_set("beta"),  baz ("def")));
    

    这都是纯 C 语言,没有宏技巧等。函数式风格,有点像柯里化。这里的好处是你可以一目了然地看到测试用例是什么。 data_set 类似——它将其参数绑定到另一个获取数据的函数:for_all 执行 thunk,检查谓词并清理。整洁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-04
      • 2019-03-26
      • 2018-10-30
      • 1970-01-01
      相关资源
      最近更新 更多