开发者

What's the value of currying in Functional Programming?

I know the conc开发者_运维问答ept and how to use of currying, but I wonder what is its value in practice?


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜