And then I've "partially applied" f. Is this what currying is?
The syntax I posted is ambivalent about which arguments you're partially applying, isn't that superior to only being able to provide the first argument?
Currying is turning a function with arity n into a sequence of unary functions.
f(x,y) = ... // arity 2
f = \x -> \y -> ... // sequence of two unary functions
f x y = ... // more compact representation of the above
Regarding partial application, your JS (?) example is basically what happens under the hood with Haskell, but without the ceremony:
f x y = ...
fByThree = f 3
fByThree y = f 3 y
Those last two are equivalent, but you don't have to include the y parameter explicitly, because f 3 returns a function that expects another parameter (like the function you wrote out explicitly).
And the Haskell version is more general, since it doesn't require a unique function to be written for each possible partial application. Of course, you can do this in languages with closures:
function fByX(x) {
return y => f(x,y);
}
fByThree = fByX(3);
But in Haskell that extra function isn't needed, it's just already there and called f. Regarding your last statement, there are also combinators in Haskell that allow you to do things like this:
fXbyThree = flip f 3
// equivalent to:
fXByThree x = f x 3
// JS
function fXByThree(x) { return f(x,3); }
// or
function fXByFixedY(y) { return x => f(x,y); }
fXByThree = fXByFixedY(3);
So I'm not sure it's strictly superior, it is more explicit though.
This is why I said to be sure to consider all the things you can do with function arguments. You can't in general assume that f(3) for a two-part argument can be read by the compiler as a curry, because the second argument may have a default. You also have to watch out in dynamic languages because the resulting function instead of a value may propagate a long way before being caught, making refactoring a challenge. (In Haskell it'll always blow up because the type system is strong, although you may not get the nicest error.) Imagine you have a whole bunch of code using this approach, and then you set a default value for the second argument. Imagine you're applying it to a whole bunch of code that wasn't written to optimize the first parameter as the one you want to partially apply.
The end result is you need a lot more noise in the partial call to indicate what it is you are doing, no matter how you slice it, and it interacts poorly with default parameters (which almost every language has nowadays) anyhow.
And then I've "partially applied" f. Is this what currying is?
The syntax I posted is ambivalent about which arguments you're partially applying, isn't that superior to only being able to provide the first argument?