28

I've recently started learning F# and come across curried functions for simple examples such as the following:

Consider a function that calculates sales by multiplying price p by number of units sold n.

let sales (p,n) = p * (float n);;

The type of this function is given as

val sales : p:float * n:int -> float

i.e. take a pair of float and int and returns a float.

We can instead write this as a curried function

let salesHi p n = p * (float n);;

The type of this function is given as

val salesHi : p:float -> n:int -> float

i.e. takes a float and returns a function of int to float.

In simple cases this seems to make no difference

sales (0.99, 100);;
salesHi 0.99 100;;

Both give

val it : float = 99.0

However with the curried function I can feed in the price for particular items to get new functions. E.g.

let salesBeer =  salesHi 5.99;;
let salesWine =  salesHi 14.99;;

Then salesBeer 2 gives 11.98 and salesWine 2 gives 29.98.

Also, I've noticed that built-in operators such as + are defined as functions, so I can write, for example:

let plus2 = (+) 2;
List.map plus2 [1;3;-1];;

and get

val it : int list = [3; 5; 1]

This seems like a good thing. So when I want to implement a function in an imperative language that would have taken n > 1 arguments, should I for example always use a curried function in F# (so long as the arguments are independent)? Or should I take the simple route and use regular function with an n-tuple and curry later on if necessary? Or something else?

How do F# programmers decide when to make a function in curried form or use a regular function with a tuple?

TooTone
  • 7,129
  • 5
  • 34
  • 60
  • Term *higher-order function* usually refers to functions accepting/returning functions, while your question seems to regard "tuples" vs "multiple arguments". You might want to reword it a little. That said, I prefer multiple-arguments functions, for the sake of currying. – Marcin Łoś Sep 10 '13 at 12:01
  • @MarcinŁoś thanks I've added a bit about the types of `sales` and `salesHi` to try to make it clearer. It's also possible I haven't used standard terminology in my question. – TooTone Sep 10 '13 at 12:12
  • What you call a 'higher order' function is actually a [curried](http://en.wikipedia.org/wiki/Currying) function vs one in tupled form. Functions are usually in curried form when using functional languages since this allows partial application. – Lee Sep 10 '13 at 12:15
  • @Lee thanks for clarification and edits. In the text I'm reading, functions like `(+)` and `sales` are known as higher-order functions and currying isn't even in the index (it appears in an exercise, however). I've put this in [another question](http://stackoverflow.com/questions/18721202/difference-between-higher-order-and-curried-functions) as it seems that I used inappropriate terminology but given what the book says and what the experienced F#ers here are saying I'm not sure what's right. – TooTone Sep 10 '13 at 14:13

4 Answers4

33

When you're choosing between curried and tupled form, the main thing to consider is whether the tuple that you'd take as an argument means anything.

Tupled form. For example, float * float might represent a range and then it is a good idea to use the tupled form.

let normalizeRange (lo, hi) = if hi < lo then (hi, lo) else (lo, hi)
let expandRange by (lo, hi) = (lo - by, hi + by)

The good thing about this is that you can then compose functions that work on ranges. You can for example write something like:

randomRange() |> normalizeRange |> expandRange 10

Curried form. On the other hand, the curried form is a better choice if the tuple of all arguments is not a stand-alone value with some useful meaning. For example, the power function pown 2.0 10 - the two arguments are the number to power and the exponent, but it is unlikely that you'd ever use the tuple (2.0, 10) somewhere else in your program.

The curried form is also useful when you have one "more important" argument, because then you can use pipelining. For example, List.map has to be curried to allow this:

[1 .. 10] |> List.map (fun n -> n + 1)
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks a lot, putting this together with your [other answer](http://stackoverflow.com/a/18721337/834521) you have given a definitive explanation of what higher order and curried functions are and when to use them. – TooTone Sep 10 '13 at 14:40
3

Or should I take the simple route and use regular function with an n-tuple and curry later on if necessary?

What do you find simpler about doing more work? let stuff x y z = ... is not just less typing than let stuff (x, y, z) it is actually less work done by the language. The second version has to allocate a tuple then destructure the tuple into arguments vs the first version which just consumes the arguments.

The curried form is the idiomatic way to write functions in F#. I really can't think of a good reason to use the tuple form unless the data was already stored as a tuple.

stonemetal
  • 6,111
  • 23
  • 25
  • Perhaps "simpler" was the wrong word to use. Maybe I should have said more familiar, as in more like the imperative languages I've been using. In the textbook I'm reading, functions taking a tuple are introduced first, e.g. `gcd : int * int -> int` for greatest common denominator, followed by what the text calls higher order functions, with few hints (so far) as to which is "better". You've answered my question very well, thank you -- the "idiomatic way" was what I was after. – TooTone Sep 10 '13 at 13:42
3

One other consideration - if you're planning on interoperating with C# or VB .NET, don't bother with the curried form as they aren't exposed that nicely to those languages. On the other hand the tupled form is exposed as a normal set of arguments from a C# / VB .NET point of view and is very natural to consume.

Isaac Abraham
  • 3,422
  • 2
  • 23
  • 26
2

The tuple form is actually a bit dangerous. It might look similar to ALGOL-style languages(=90% of popular languages) - but it works differently.

Consider this:

foo bar(x,y)

In all ALGOL-style languages that allow this syntax, this means "call bar with x and y as arguments, and pass the results to foo" - when foo doesn't have to be a method(it can be syntax, like print in 2.* Python).

However, in Lambda-style languages(like ML, Haskell and F#), this means "call foo with bar as argument, and then call the result(which is a function) with the tuple (x,y) as argument.

If you are new to Lambda-style languages, this can be quite confusing. Now, if you were using the curry form, the equivalent would be:

foo bar x y

which is just as wrong as foo bar(x,y) - but not nearly as confusing! Even programmers that are not familiar with Lambda-style languages can easily figure that bar is not the first function that will be called in foo bar x y. The error is clear right away.

Idan Arye
  • 12,402
  • 5
  • 49
  • 68
  • Thanks I'm working through this. Minor point, your first example fails with `error FS0597: Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized`. You need a space, as in `foo bar (x,y)`. In a way this illustrates your point! – TooTone Sep 10 '13 at 16:31
  • That's interesting thanks, it certainly explains the errors when I forget to bracket args to `printfn`! My main comment is that the F# compiler will help you out. As a practical example, write `let bar (x,y) = (y,x)`. **Case 1**: `foo` takes a pair, write `let foo (x,y) = (min x y, max x y)`. Trying `foo bar(1,2)` gives a "use spaces" error, and `foo bar (1,2)` also fails. Whereas both `foo (bar (1,2))` and `foo(bar (1,2))` work. **Case 2**: `foo` takes a function and a pair, write `let foo f (x,y) = f (min x y, max x y)`: `foo bar(1,2)` and `foo (bar (1,2))` fail; only `foo bar (1,2)` works. – TooTone Sep 10 '13 at 16:52
  • Actually, I've never really learned F#, but separating arguments with space is not required in ML or Haskell(as long as they are separate identifiers ofcourse). I figured that F# - which derives from ML - will act the same. Microsoft probably made that rule to prevent the error I described. – Idan Arye Sep 10 '13 at 17:56