In ML all functions take exactly one argument. But when we need to send more than one argument, ML interprets the arguments in the traditional parentheses as a single tuple, in keeping with the one argument only rule:
fun swap1 (i,j) = (j,i)
: val swap1 = fn : 'a * 'b -> 'b * 'a
The two arguments above separated by the * means they are a 2-arity tuple.
- swap1 (1,2);
val it = (2,1) : int * int
Even when variables seem separate, they are still considered a single argument, again, a 2-arity tuple:
fun swap1a (i : int, j : int) = (j,i)
: val swap1 = fn : int * int -> int * int
If we create a type synonym with type, e.g.,
type int_pair = int * int
functions using int_pair still are typed as tuples:
fun swap2 ((i,j) : int_pair) = (j,i)
: val swap2 = fn : int_pair -> int * int
fun swap2a (ip : int_pair) = (#2 ip, #1 ip)
: val swap2a = fn : int_pair -> int * int
fun swap2b (ip : int*int) = (#2 ip, #1 ip)
: val swap2b = fn : int * int -> int * int
ML also has curried functions where multiple arguments are curried, basically meaning the arguments are taken in one at a time, which, again, holds to the only-one-argument rule. Currying allows us to partially apply a function to some of its arguments, leaving a residual function which can be further evaluated later. The special ML syntax for a function that employs currying is to simply have the arguments without enclosing parentheses and separated by a space:
fun swap3 i j = (j,i)
: val swap3 = fn : 'a -> 'b -> 'b * 'a
- swap3 1 2;
val it = (2,1) : int * int
Another visualization is to construct swap3 with anonymous functions:
- val swap3a = fn i => fn j => (j,i)
- swap3a 1 2;
val it = (2,1) : int * int
Now, we might envision the incoming 1 2 being resolved right-to-left (ironically called left-associating) a la lambda calculus:
(fn i => fn j => (j,i))(1,2)
(fn i => (2,i))(1)
(2,1)
Notice that fn i =>... is actually accepting a function as its input, in this example, the anonymous function expression fn j =>..., which has the value (2,i). In fact, the spirit of this can be done at the REPL:
- ((fn i => fn j => (j,i)) 1) 2;
val it = (2,1) : int * int
As commenter Andreas Rossberg says (see above comments), in most functional languages, multiple argument functions employ currying. He notes that the use of tuples is cartesian in the sense of a cartesian product. He also notes that this is orthogonal, which in this context might mean that these two approaches to handling multiple arguments, tuple and currying, are orthogonal to each other in that they're completely opposite, non-overlapping approaches that don't semantically interfere with one another.
(i, j)is not "tuple-like", it is a tuple. All functions take exactly one argument. - molbdnilofun sip4 ((i,j) : int * int), andfun sip5 ((i : int, j : int) : int*int)andfun sip6 ((i : int, j : int) : int_pair)- molbdnilofun sip7 (ip: int * int) = ip : int_pair;, you're greeted withval sip7 = fn : int * int -> int_pair. - molbdnilofun sip3 (i : int, j : int) = (j,i)is not getting two variables, but one packaged as a tuple. So why are clearly two incoming dependent variables considered just one? What is the computer science theory behind this? -- my original question could be worded as. Is this something from the lambda calculus world? I've seen ML tutorials that start you out with lambda calculus. . . . - 147pmsip1andsip3use pattern matching. Pattern-matching is able to unpack the tuple, binding the items toiandj(in this case). That SML function definitions are able to use pattern matching doesn't imply that the functions so defined are functions of more that 1 variable. - John Coleman