Thatâs correct, itâs awesome you figured it out and understood. Itâs actually not about functional languages but about math. Specifically, something called lambda calculus (by Alonzo Church, predating even computers!).
calc
is a function takes a Float
as input, and returns a function (which takes a Float
and returns a function which takes a Float
and returns a Float
).
calc f1
is a function that takes a Float
and returns a function (which takes a Float
and returns a Float
).
calc f1 f2
is a function that takes a Float
and returns a Float
.
The second line is the definition, and it says that the function calc needs three arguments to calculate the product and then it states what to do with those arguments.
Right, so your ânormal intentionâ is to write a function like:
calc :: (Float, Float, Float) -> Float
And you could write it this way if you wanted. Even this way, the function calc
actually takes ONLY ONE argument, not three! It takes âonly one thingâ that happens to be a 3-tuple.
If you had written it that way, the only way to use the function would be:
Prelude> calc (1, 2, 3)
6.0
and it would be illegal to use it with fewer numbers:
*Main> calc 1
<interactive>:5:6: error:
⢠No instance for (Num (Float, Float, Float))
arising from the literal â1â
⢠In the first argument of âcalcâ, namely â1â
In the expression: calc 1
In an equation for âitâ: it = calc 1
This way, we donât get the âbonus functionsâ like calc f1
or calc f1 f2
. So we lose the convenience of partial application. There might be situations where we want to use the same function (multiplying numbers) on fewer arguments, so we would have to write separate functions to do that.
So if you write it in a curried way, you write ONE definition, and Haskell figures out 3 functions out of that for you!
And, if you write it in a curried form, it still takes ONLY ONE argument! So whether you define the function in a âtupledâ way or in a âcurriedâ way, it always takes one argument!
If we wanted to define the partially applied functions ourselves, we would have to use the lambdas, and the definition would look like this:
calc = (\f1 -> (\f2 -> (\f3 -> f1*f2*f3)))
So with currying, there is not necessarily a âspecial, new, different order of computationâ going on. Because there are many different strategies for computing lambda expressions. Iâm not sure how Haskell evaluates them. (Probably it does optimizations and chooses different strategies based on different situations.)
My advice would be to let go of your âimperative thinkingâ about evaluation (like âOK this first, then this, then this, then thisâ) and to try thinking in a more mathematical way (meaning, in terms of functions). You can trust Haskell that it computes things in a good way.
These ideas come from mathematics. Functions with these kind of type signatures (higher order functions that consume functions as input and/or return functions as output) and partial application of them are used all over mathematics.
For example we can consider a (differentiable) function from the real numbers to the real numbers:
f: R -> R
Then we can consider, for example, a âdifferential operatorâ D
that consumes such a function as input, and returns its derivative as output:
D: (R -> R) -> (R -> R)
Or we could consider a function that measures some notion of âdistanceâ between two functions (not two points, but two functions)
d(f, g) := limsup { |f(x) - g(x)| | x in R }
The signature of d
is ((R -> R) x (R -> R)) -> R
, that is, it takes a pair of real-to-real functions, and returns a real number.
But we could do a partial application of only one argument if we wanted. Holding the first argument fixed. This would give us a new distance function d_f
that measures âdistance from fâ rather than âdistance between two functionsâ:
d_f (g) := d(f, g)
Then d_f
has signature (R -> R) -> R
.
So you can go nuts with this kind of thing. And they do. Because mathematicians need higher and higher levels of abstraction for their theories. Itâs all down to mathematics. Functional languages simply make a greater effort to bring mathematical theory into code, compared to other languages, thatâs all. For example Curry (whom âcurryingâ is named after) was a mathematician/logician.
Moral of the story: study math and logic! Everything else follows from that.