Hacker News new | ask | show | jobs
by markov_twain 4885 days ago
Why has arithmetic become the universal example of currying and partial function application?

Examples: "Currying", Section 9.3 of Programming in Scala (addition); Ruby 1.9.3 documentation for Proc#curry http://www.ruby-doc.org/core-1.9.3/Proc.html#method-i-curry (addition); "Currying" at the Haskell Wiki http://www.haskell.org/haskellwiki/Currying (division); "Currying" at Wikipedia http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_... (division); "Currying" at c2 wiki http://c2.com/cgi/wiki?CurryingSchonfinkelling (addition, multiplication); PEP 0309 for adding curry to Python http://www.python.org/dev/peps/pep-0309/ (addition); this StackExchange post; etc.

Surprisingly, searching for currying in JavaScript returns this John Resig post with actually useful examples: http://ejohn.org/blog/partial-functions-in-javascript/ although they still have no advantage over doing partial application by just defining a new function.

The only situation in which I've ever thought to myself "I know, I'll use curried functions," was in passing a set of data through a bunch of filters with different arities.

To take an example from HN's front page today, say you want to take some map of key value pairs, grab all the keys, convert them to strings, reject duplicates, sort them alphabetically, and finally concatenate them separated by commas.

Let's assume you have the following functions available (in pseudocode):

  keys(map) -> vector
  flatmap(func, vector) -> vector
  tostring(anything) -> string
  sort(sort-func, vector) -> vector
  compare-strings(string, string) -> first-is-greater, theyre-equal, or last-is-greater
  unique(compare-func, vector) -> vector
  concat(separator, vector) -> vector
and assume you have some way of connecting a list of these filter functions together like pipes, where the output of each function becomes the input of the next:

  reduce(composing-func, list-of-filter-functions, initial-input) -> final-result
Here, the composing-func takes two arguments: input, which is the output of the last iteration (or initial-input); and filter, which represents each function that will be applied in turn to produce some output. Notice that each filter function must take exactly one argument, which matches the return type of the previous filter-function in the pipeline.

  composing-func = (previous-result, filter) -> {
    apply(filter, previous-result)
  }
Now looking at our available functions, we can use currying and partial function application to pre-fill-in some arguments needed so that their input and output types line up:

  list-of-filter-functions = [
    flatmap(keys),
    map(tostring),
    unique(compare-strings),
    sort(compare-strings),
    concat(",")
  ]
And actually, we're now done. All of our filter functions have been partially applied as a sort of initialization for a complicated pipeline.

I'm guessing that currying and partial function application are useful in building parsers (like haskell & scala's parser combinators) but I don't know.

1 comments

I think people use currying with arithmetic examples because currying gives you general partial application if your function is commutative, and almost the only commutative functions in real programs are arithmetic and some comparison functions.

It's kind of weird because the more arguments your function has, the more useful partial application is, and the less likely the curried form is going to work without reordering.