'Putting things' (multi-argument function calls, in this case) 'in-band doesn't make them go away, but it does successfully hide them from your tooling', part 422.
Seems like a disaster to use s-expressions for a language like that. I love s-expressions but they only make sense for variadic languages. The entire point of them is to quickly delimit how many arguments are passed.
In say Haskell `f x y z` is the same thing as `(((f x) y) z)`. That is definitely not the case with s-expressions; braces don't delimit; they denote function application. It's like saying that `f(x,y,z)` being the same as `f(x)(y)(z)` which it really isn't. The point of s-expressions is that you often find yourself calling functions with many arguments that are themselves a result of a function application, at that point `foo(a)(g(a,b), h(x,y))` just becomes easier to parse as ((foo a) (g a b) (h x y))`.
(f x y z)
Is equivalent to: (f . (x . (y . (z . ())))
Every function takes one argument - a list.Lists make partial application simpler than with tuples (at least Haskell style tuples), because we don't need to define a new form for each N-sized tuple. Eg, in Haskell you'd need:
partial2 : (((a, b) -> z), a) -> (b -> z)
partial3 : (((a, b, c) -> z), a) -> ((b, c) -> z)
partial4 : (((a, b, c, d) -> z), a) -> ((b, c, d) -> z)
...
With S-expressions, we can just define a partial application which takes the first argument (the car of the original parameter list) and returns a new function taking a variadic number of arguments (the cdr of the original parameter list). Eg, using a Kernel operative: ($define! $partial
($vau (f first) env
($lambda rest
(eval (list* f first rest) env))))
($define! f ($lambda (x y z) (+ x y z)))
(f 3 2 1)
=> 6
($define! g ($partial f 3))
(g 2 1)
=> 6
($define! h ($partial g 2))
(h 1)
=> 6
($define! i ($partial h 1))
(i)
=> 6
We could perhaps achieve the equivalent in Haskell explicitly with a multi-parameter typeclass and a functional dependency. Something like: class Partial full first rest | full first -> rest where
partial :: (full -> z, first) -> (rest -> z)
instance Partial ((a,b)) a b where
partial (f, a) = \b -> f (a, b)
instance Partial ((a, b, c)) a ((b, c)) where
partial (f, a) = \(b, c) -> f (a, b, c)
instance Partial ((a, b, c, d)) a ((b, c, d)) where
partial (f, a) = \(b, c, d) -> f (a, b, c, d)
...That's one way to look at it, but the major difference is that one can also pass a a list as one argument, as in `(f x y z)` and `(f (list x y z)` are not the same. The thing with tuples is that a tuple of one datum is the very same as that datum itself and the same is true with the currying situation. `(f x) y` and `f x y` are truly one and the same in Haskell and Ocaml, just as `f(x, y)` and `let val a = (x,y) in f x` are one and the same in SML. This is not the case in Rust where `f(x,y)`, a function called with two arguments, and `f((x,y))`, a function called with one argument that is a tuple of two arguments are two different things.
There is also a difference in Scheme between returning a single value that is a list containing multiple values, and actually returning multiple values, In Rust however there is no difference between returning two values and returning a pair of two values. So Rust functions actually do properly take multiple arguments but always return a single one which may or may not be a tuple. In SML, Haskell and OCaml all functions technically take only one argument and return one value.