函数式编程中柯里化的价值是什么?

发布于 2024-11-18 19:20:07 字数 39 浏览 2 评论 0原文

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

I know the concept and how to use of currying, but I wonder what is its value in practice?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

叹沉浮 2024-11-25 19:20:07

正如相关问题所涵盖的,实际使用柯里化函数?,有很多原因人们重视柯里化并利用它,包括:

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

As the related question covers, Practical use of curried functions? , there are many reasons why people value currying and make use of it, including:

  • improving code reuse -- special case functions are simply partially-applied (and curried) generic functions
  • improving code readability -- map (+2) is easier to read than map (\x -> x + 2)
  • improved performance -- currying can make obvious certain specializations, and a good compiler will generate the specialized version for you
  • fun -- simpler code , more beautiful code makes life more enjoyable.
昔日梦未散 2024-11-25 19:20:07

我发现的真正好处是:

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

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

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

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

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

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

Real benefits I have found:

  • Less bugs - composing code by function composition tends to result in more correct code than imperative control flow. For example, if you use "map" rather than "for loops" you eliminate the risk of many "off by one" indexing errors

  • Better concurrency - and code you create with pure, side-effect free functions is automatically thread safe. Couple this with immutable persistent data structures and you have a great recipe for writing robust concurrent code. Clojure is particularly good for this - see http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

  • More concise and manageable code - functional code seems in my completely unscientific analysis to be considerably shorter than imperative OOP code. The benefit tends to be more pronounced as programs get larger. I think there a several reasons for this:

    • The syntax of functional languages tends to be pretty concise. Haskell and the various Lisps for example both have very simple and elegant syntaxes
    • Functional languages tend to encourage composition (of higher order functions) rather than "gluing" parts of programs together. Hence you avoid a lot of the boilerplate that is inherent in many other paradigms.
    • Related to the composition point, I find that it's easier to apply the DRY principle in functional languages. If you see a common pattern, it's almost always possible to extract this pretty easily into a higher order function (or macro if you're a Lisper) so that you can use it elsewhere.
  • Testability - when you write code primarily with pure functions, it's very easy to write robust tests.

There are some downsides to ofset this of course:

  • It's harder to write - you need more mental agility because you need to hold pretty complex abstract concepts in your head. It helps if you're a trained mathematician (I am) but I still find writing functional code harder than OOP.
  • There is some performance overhead - functional languages use various contructs that necessarily imply some degree of overhead. Although this can be made pretty small with good compilers, it can never be completely eliminated.
  • Library / tool suport - this is almost entirely due to the greater maturity of OOP platforms and tools, but it's still an issue nevertheless. In the long run this won't be an issue but the best solution I've found is to use Clojure which can benefit from most tof the Java platform libraries and tools.
疾风者 2024-11-25 19:20:07

我想说这有点像一次且仅一次

在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、检查谓词并进行清理。整齐的。

I would say it's a bit like Once and Only Once

In C/C++ I find myself writing code such as

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

Instead of writing the for-loop once for each of alpha and beta. This is analagous to currying in procedural code. The advantage here is clear.

Currying is an important theoretical concept, but in practical terms, this is the advantage.

In fact, I remember writing a test suite in C once, it was a bit like this:

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

This was all in pure C, no macro trickery etc. Functional-style and kind of like currying. Here the advantage is you can see at a glance exactly what the test cases are. data_set is similar -- it binds its argument to another function which fetches the data: for_all executes the thunk, checks the predicate, and cleans up. Tidy.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文