I think the author is trying hard to clarify something that is (a) interesting and (b) usually described in ridiculously and pointlessly complicated terms. I would have gone further, but I’m not very sympathetic to Haskell at all.
This seems like a super interesting article and I’m currently digging into it. At the outset, it isn’t clear to me why there is a space between the name of the function and its arguments.
The article says “Functions are called with a space between the name and parenthesis. This is done to simulate calling functions by juxtaposition, and it’s helpful for calling curried functions.” but that doesn’t actually help me understand anything. What does “calling functions by juxtapostiion” mean, and why is the space helpful for calling curried functions?
Hey, glad you find it interesting. The space isn’t required, and you could call it without a space there, but in languages like Haskell where currying is more common than JavaScript, functions are called by “juxtaposition”, where arguments are separated by a space. In JavaScript, calling a function requires parenthesis, and calling curried functions would look like f(x)(y)(z). I added that note there just to clarify the notation because it’s not common to call functions this way, but I think f (x) (y) (z) looks nicer.
I’ll edit it to make it clear that it’s not required and purely there for aesthetic purposes, thank you!
It’s not much. In some math texts it is common to write “f x” instead of “f(x)” - that is the juxtaposition which means just “put things next to each other”. That notation is nice, less typing and easy to read, except when there are multiple arguments and function valued expression or other forms of ambiguity. Does f x g y mean f(x,g,y) or f(x)(g (y)) or some other variant. “Currying” just means packing multiple arguments into one as a form of notational simplicity. e.g. f(x,y,z) is f(q) where we’ve done something to stuff x,yz into q or into f itself so add(a,b,c) could be add’(c) where add’(x) = add(a,b,x) or something. Seems pointless in this case.
If you can’t do
int x;
x = f(0);
x = g(x);
because your programming language does not have variable assignment, you need to do either
g(f(0))
which can become too awkward with complex formulas
or have a fancy ;
so f(0) ;; g(_)
means “I will avert my eyes while the compiler does an assignment to a temp variable and pretend it didn’t happen”
“Currying” just means packing multiple arguments into one as a form of notational simplicity
I don’t think that is accurate. Currying is about transforming functions that get multiple parameters into a sequence of functions, each of which takes one argument.
That is still a somewhat imprecise description, but I think an example would be more clarifying than going deeper into the theoretical details: If we take sum as non-curried function, it takes two integers to produce another one, the type is something like (Int x Int) -> Int, and in practice you call it like sum(1, 2) which evaluates to 3.
The curried version would be one function that given an integer a returned a function that given an integer b returned a+b. That is, sum is transformed into the type Int -> (Int -> Int). Now to calculate 1 + 2 we should first apply sum to get a function that sums one, like sum1 = sum(1), where sum1 is a function with type Int -> Int; and then apply this new sum1 function to 2, as in sum1(2) which returns 3. Or in short, dropping the temporary variable sum1, we can apply sum(1) directly as in (sum(1))(2), and get our shiny 3 as result.
If your language uses space to denote function application, then you can write that last application as (sum 1) 2. Finally, if application is left associative, you can drop the parenthesis and get to sum 1 2, which, is arguably, pleasant syntax.
I have a negative attitude to mystification. I don’t like it when reasonably simple programming techniques are claimed to be deep mysterious mathematical operations. Your example, is, of course, an example of why these “fancy semicolons” are needed when it is scaled up. Imagine how hard it would be to track a state variable, say an IO file descriptor around a program if we had to do fd_1, fd_2, fd_n every time there was an i/o operation - keeping these in order would be painful. The “combinator” automates the bookeeping.
The explanation of Currying is perfectly correct, I think, but I’d like to hear what you think I got wrong. There’s not much to it.
In much of mathematics, all of this is just a notational convention, not a endofunctor on a category of sets. The author, who is at least trying to make things clear, could have simply written:
The notation is simpler if we write “f x” to indicate function composition instead of “f(x)”, otherwise it gets cluttered with parenthesis. To avoid ambiguity with multiple arguments, only single argument functions are allowed and we take care of multiple arguments by using functions that produce functions as values so, instead of f(x,y,z) the value of f1 x is a map f2 and f2 y produces a value f3 and f3 z then is equal to f(x,y,z). Equivalently, f(x,y,z) = ((f1 x) y) z.
I think FP is an interesting approach. I wish it could be treated as a programming style instead of as Category and Lambda Calculus Notational Tricks To Impress your Colleagues.
Functions take only one argument. So it’s really taking a (x * y * z), or an ‘x’ and a ‘y’ and a ‘z’. Currying is taking a function that takes an (x * y * z) -> w and transforms it to (x -> (y -> (z -> w))) aka (x -> y -> z -> w) this is useful because it allows us to create little function generators by simply failing to provide all the arguments. This isn’t simply a notation difference though because they are completely different types and this can matter quite a bit in practice. While yes there is a one to one correspondence between the two, that’s not the same as a “notational difference”. Tuples are fundamentally different objects than functions that return functions and this difference matters on a practical level when you go to implement and use them. You can say that it’s simply a notational difference but it’s untrue, and implicit conversions from one to the other does not mean they are “the same thing”.
In essence, it doesn’t depend. The notation depends but the notation represents one thing. f(x,y,z,w) is an implicit tuple of x,y,z,w. Without this there’s no concept of like domain of a function. This is all philosophical waxing but once you talk about currying it starts to matter because it affects what things are possible because not all arguments are provided at once. You could argue that objects and mutability sidestep this but I’d also argue that mutation within a function begins to deviate from a well defined “mathematical” function. That may be fine for you, and that’s okay. However since this conversation is primarily about definitions and we talked specifically about the mathematical way of modeling programming with functions yknow it matters.
For example in javascript the difference between f(x)(y)(z) and f(x,y,z) is literally different computations, and while there are times you can convert between the two freely, there are things that f(x)(y)(z) can do that f(x,y,z) cannot. For example I can use f(x)(y), to create a callback to defer computation with because f(x)(y) returns a function which takes a “z”. That’s genuinely useful. Now you can meaningfully argue that with objects you can do the same thing and that’s great but this is about functions and function passing. So it is actually meaningful to describe what you can and cannot do with these things.
This is all philosophical waxing but once you talk about currying it starts to matter because it affects what things are possible because not all arguments are provided at once. You could argue that objects and mutability sidestep this but I’d also argue that mutation within a function begins to deviate from a well defined “mathematical” function.
Very little in programming is a mathematical function. Even something like (==) : a -> a -> bool isn’t a function, as its domain would be the universal set.
Also, I don’t think all mathematical functions are curryable? Like consider the function f[x: R^n] = n, which returns the number of arguments passed in. I don’t think there’s a way to curry that.
A year or two ago I wrote an Idris function that took an arbitrary number of (curried) arguments and put them in a list (and gave you the argument count). From what I remember it used a type class with one instance for (->) (the accumulator) and one for the result type, and a lot of type inference. I’ll dig it out later.
Edit: That was an already-curried function. You could potentially automatically curry f[x: R^n] using the same technique since, in Idris, tuples are constructed like lists: (1, 2, 3) is the same as (1, (2, (3, ()))), so you could deconstruct a cartesian product while simultaneously producing a chain of (->) function constructors.
Both of these points while interesting, and I think important questions to ask, don’t affect the position that currying is not simply a notational difference.
In essence, of course it depends, but it’s trivia. The course notes for the mulivariate calc course at MIT begins “Goal of multivariable calculus: tools to handle problems with several parameters – functions of
several variables.” I’m amazed that some Haskellian or Curry Fan has not intervened to correct the poor fool teaching that course all wrong - “Excuse me, but those are not multiple variables, they are vectors! Please write it down.” If you did say that, and the Professor was in a good mood, she’d probably say: yes, that’s another way to look at the same thing.” And if you then insisted that “a well defined mathematical function” has a single variable, well … As usual, the problem here is that some trivial convention of the Haskell approach is being marketed as the one true “mathematics”.
I have no idea what a “mutation within a function” would mean in mathematics.
Programs don’t have mathematical functions, they have subroutines that may implement mathematical functions. There is no such thing as a callback in the usual mathematical definition of a function. Obviously, within a programming language, there will be differences between partial computation or specialization of a function and functions on lists. You seem to be mixing up what programming operations are possible on subroutine definition with the mathematical definition of a function - unless I’ve totally missed your point. Obviously, for programs there is a big difference between a vector and a specialized function. But so what?
I think the author is trying hard to clarify something that is (a) interesting and (b) usually described in ridiculously and pointlessly complicated terms. I would have gone further, but I’m not very sympathetic to Haskell at all.
is really pretty good.
This seems like a super interesting article and I’m currently digging into it. At the outset, it isn’t clear to me why there is a space between the name of the function and its arguments.
The article says “Functions are called with a space between the name and parenthesis. This is done to simulate calling functions by juxtaposition, and it’s helpful for calling curried functions.” but that doesn’t actually help me understand anything. What does “calling functions by juxtapostiion” mean, and why is the space helpful for calling curried functions?
Hey, glad you find it interesting. The space isn’t required, and you could call it without a space there, but in languages like Haskell where currying is more common than JavaScript, functions are called by “juxtaposition”, where arguments are separated by a space. In JavaScript, calling a function requires parenthesis, and calling curried functions would look like
f(x)(y)(z)
. I added that note there just to clarify the notation because it’s not common to call functions this way, but I thinkf (x) (y) (z)
looks nicer.I’ll edit it to make it clear that it’s not required and purely there for aesthetic purposes, thank you!
It’s not much. In some math texts it is common to write “f x” instead of “f(x)” - that is the juxtaposition which means just “put things next to each other”. That notation is nice, less typing and easy to read, except when there are multiple arguments and function valued expression or other forms of ambiguity. Does f x g y mean f(x,g,y) or f(x)(g (y)) or some other variant. “Currying” just means packing multiple arguments into one as a form of notational simplicity. e.g. f(x,y,z) is f(q) where we’ve done something to stuff x,yz into q or into f itself so add(a,b,c) could be add’(c) where add’(x) = add(a,b,x) or something. Seems pointless in this case.
If you can’t do int x; x = f(0); x = g(x); because your programming language does not have variable assignment, you need to do either g(f(0))
which can become too awkward with complex formulas or have a fancy ; so f(0) ;; g(_)
means “I will avert my eyes while the compiler does an assignment to a temp variable and pretend it didn’t happen”
I don’t think that is accurate. Currying is about transforming functions that get multiple parameters into a sequence of functions, each of which takes one argument.
That is still a somewhat imprecise description, but I think an example would be more clarifying than going deeper into the theoretical details: If we take sum as non-curried function, it takes two integers to produce another one, the type is something like
(Int x Int) -> Int
, and in practice you call it likesum(1, 2)
which evaluates to 3.The curried version would be one function that given an integer
a
returned a function that given an integerb
returneda+b
. That is, sum is transformed into the typeInt -> (Int -> Int)
. Now to calculate 1 + 2 we should first apply sum to get a function that sums one, likesum1 = sum(1)
, wheresum1
is a function with typeInt -> Int
; and then apply this newsum1
function to 2, as insum1(2)
which returns 3. Or in short, dropping the temporary variablesum1
, we can applysum(1)
directly as in(sum(1))(2)
, and get our shiny 3 as result.If your language uses space to denote function application, then you can write that last application as
(sum 1) 2
. Finally, if application is left associative, you can drop the parenthesis and get tosum 1 2
, which, is arguably, pleasant syntax.Just use a different variable as you should in the first case as well and it works just fine.
Also that’s a bad explanation of currying.
You have a negative reaction to FP for some reason which leads you to write these cheap jabs that are misinformative.
I have a negative attitude to mystification. I don’t like it when reasonably simple programming techniques are claimed to be deep mysterious mathematical operations. Your example, is, of course, an example of why these “fancy semicolons” are needed when it is scaled up. Imagine how hard it would be to track a state variable, say an IO file descriptor around a program if we had to do fd_1, fd_2, fd_n every time there was an i/o operation - keeping these in order would be painful. The “combinator” automates the bookeeping.
The explanation of Currying is perfectly correct, I think, but I’d like to hear what you think I got wrong. There’s not much to it.
In much of mathematics, all of this is just a notational convention, not a endofunctor on a category of sets. The author, who is at least trying to make things clear, could have simply written:
I think FP is an interesting approach. I wish it could be treated as a programming style instead of as Category and Lambda Calculus Notational Tricks To Impress your Colleagues.
Functions take only one argument. So it’s really taking a (x * y * z), or an ‘x’ and a ‘y’ and a ‘z’. Currying is taking a function that takes an (x * y * z) -> w and transforms it to (x -> (y -> (z -> w))) aka (x -> y -> z -> w) this is useful because it allows us to create little function generators by simply failing to provide all the arguments. This isn’t simply a notation difference though because they are completely different types and this can matter quite a bit in practice. While yes there is a one to one correspondence between the two, that’s not the same as a “notational difference”. Tuples are fundamentally different objects than functions that return functions and this difference matters on a practical level when you go to implement and use them. You can say that it’s simply a notational difference but it’s untrue, and implicit conversions from one to the other does not mean they are “the same thing”.
In Haskell functions may take only one argument. In mathematics and in programming languages, it depends.
https://math.stackexchange.com/questions/2394559/do-functions-really-always-take-only-one-argument
In essence, it doesn’t depend. The notation depends but the notation represents one thing. f(x,y,z,w) is an implicit tuple of x,y,z,w. Without this there’s no concept of like domain of a function. This is all philosophical waxing but once you talk about currying it starts to matter because it affects what things are possible because not all arguments are provided at once. You could argue that objects and mutability sidestep this but I’d also argue that mutation within a function begins to deviate from a well defined “mathematical” function. That may be fine for you, and that’s okay. However since this conversation is primarily about definitions and we talked specifically about the mathematical way of modeling programming with functions yknow it matters.
For example in javascript the difference between f(x)(y)(z) and f(x,y,z) is literally different computations, and while there are times you can convert between the two freely, there are things that f(x)(y)(z) can do that f(x,y,z) cannot. For example I can use f(x)(y), to create a callback to defer computation with because f(x)(y) returns a function which takes a “z”. That’s genuinely useful. Now you can meaningfully argue that with objects you can do the same thing and that’s great but this is about functions and function passing. So it is actually meaningful to describe what you can and cannot do with these things.
Very little in programming is a mathematical function. Even something like
(==) : a -> a -> bool
isn’t a function, as its domain would be the universal set.Also, I don’t think all mathematical functions are curryable? Like consider the function
f[x: R^n] = n
, which returns the number of arguments passed in. I don’t think there’s a way to curry that.A year or two ago I wrote an Idris function that took an arbitrary number of (curried) arguments and put them in a list (and gave you the argument count). From what I remember it used a type class with one instance for
(->)
(the accumulator) and one for the result type, and a lot of type inference. I’ll dig it out later.Edit: That was an already-curried function. You could potentially automatically curry
f[x: R^n]
using the same technique since, in Idris, tuples are constructed like lists:(1, 2, 3)
is the same as(1, (2, (3, ())))
, so you could deconstruct a cartesian product while simultaneously producing a chain of(->)
function constructors.Both of these points while interesting, and I think important questions to ask, don’t affect the position that currying is not simply a notational difference.
In essence, of course it depends, but it’s trivia. The course notes for the mulivariate calc course at MIT begins “Goal of multivariable calculus: tools to handle problems with several parameters – functions of several variables.” I’m amazed that some Haskellian or Curry Fan has not intervened to correct the poor fool teaching that course all wrong - “Excuse me, but those are not multiple variables, they are vectors! Please write it down.” If you did say that, and the Professor was in a good mood, she’d probably say: yes, that’s another way to look at the same thing.” And if you then insisted that “a well defined mathematical function” has a single variable, well … As usual, the problem here is that some trivial convention of the Haskell approach is being marketed as the one true “mathematics”.
I have no idea what a “mutation within a function” would mean in mathematics. Programs don’t have mathematical functions, they have subroutines that may implement mathematical functions. There is no such thing as a callback in the usual mathematical definition of a function. Obviously, within a programming language, there will be differences between partial computation or specialization of a function and functions on lists. You seem to be mixing up what programming operations are possible on subroutine definition with the mathematical definition of a function - unless I’ve totally missed your point. Obviously, for programs there is a big difference between a vector and a specialized function. But so what?
You have confused operads with categories, I think.
You can be upset about it, but currying isn’t simply a notational difference from multivariate functions.