Remix.run Logo
messe 8 hours ago

What benefit does drawing a distinction between parameter list and single-parameter tuple style bring?

I'm failing to see how they're not isomorphic.

Kambing 7 hours ago | parent | next [-]

They are isomorphic in the strong sense that their logical interpretations are identical. Applying Curry-Howard, a function type is an implication, so a curried function with type A -> B -> C is equivalent to an implication that says "If A, then if B, then C." Likewise, a tuple is a conjunction, so a non-curried function with type (A, B) -> C is equivalent to the logic statement (A /\ B) -> C, i.e., "If A and B then C." Both logical statements are equivalent, i.e., have the same truth tables.

However, as the article outlines, there are differences (both positive and negative) to using functions with these types. Curried functions allow for partial application, leading to elegant definitions, e.g., in Haskell, we can define a function that sums over lists as sum = foldl (+) 0 where we leave out foldl's final list argument, giving us a function expecting a list that performs the behavior we expect. However, this style of programming can lead to weird games and unweildy code because of the positional nature of curried functions, e.g., having to use function combinators such as Haskell's flip function (with type (A -> B -> C) -> B -> A -> C) to juggle arguments you do not want to fill to the end of the parameter list.

messe 7 hours ago | parent [-]

Please see my other comment below, and maybe re-read the article. I'm not asking what the difference is between curried and non-curried. The article draws a three way distinction, while I'm asking why two of them should be considered distinct, and not the pair you're referring to.

Kambing 3 hours ago | parent [-]

Apologies, I was focused on the usual pairing in this space and not the more subtle one you're talking about. As others have pointed out, there isn't really semantic a difference between the two. Both approaches to function parameters produce the same effect. The differences are purely in "implementation," either theoretically or in terms of systems-building.

From a theoretical perspective, a tuple expresses the idea of "many things" and a multi-argument parameter list expresses the idea of both "many things" and "function arguments." Thus, from a cleanliness perspective for your definitions, you may want to separate the two, i.e., require function have exactly one argument and then pass a tuple when multiple arguments are required. This theoretical cleanliness does result in concrete gains: writing down a formalism for single-argument functions is decidedly cleaner (in my opinion) than multi-argument functions and implementing a basic interpreter off of this formalism is, subsequently, easier.

From a systems perspective, there is a clear downside in this space. If tuples exist on the heap (as they do for most functional languages), you induce a heap allocation when you want to pass multiple arguments! This pitfall is evident with the semi-common beginner's mistake with OCaml algebraic datatype definitions where the programmer inadvertently wraps the constructor type with parentheses, thereby specifying a constructor of one-argument that is a tuple instead of a multi-argument constructor (see https://stackoverflow.com/questions/67079629/is-a-multiple-a... for more details).

emih 7 hours ago | parent | prev | next [-]

That's a fair point, they are all isomorphic.

The distinction is mostly semantic so you could say they are the same. But I thought it makes sense to emphasize that the former is a feature of function types, and the latter is still technically single-parameter.

I suppose one real difference is that you cannot feed a tuple into a parameter list function. Like:

fn do_something(name: &str, age: u32) { ... }

let person = ("Alice", 40);

do_something(person); // doesn't compile

recursivecaveat 7 hours ago | parent | prev | next [-]

Probably just that having parameter-lists as a specific special feature makes them distinct from tuple types. So you may end up with packing/unpacking features to convert between them, and a function being generic over its number of parameters is distinct from it being generic over its input types. On the other hand you can more easily do stuff like named args or default values.

layer8 7 hours ago | parent | prev | next [-]

The parameter list forces the individual arguments to be visible at the call site. You cannot separate the packaging of the argument list from invoking the function (barring special syntactic or library support by the language). It also affects how singleton tuples behave in your language.

The article is about programmer ergonomics of a language. Two languages can have substantially different ergonomics even when there is a straightforward mapping between the two.

rocqua 6 hours ago | parent | prev | next [-]

It's not that they are meaningfully different. It's just acknowledging if you really want currying, you can say 'why not just use a single parameter of tuple type'.

Then there's an implication of 'sure, but that doesn't actually help much if it's not standar' and then it's not addressed further.

Pay08 8 hours ago | parent | prev | next [-]

The tuple style can't be curried (in Haskell).

messe 8 hours ago | parent [-]

That's not what I'm talking about.

The article draws a three way distinction between curried style (à la Haskell), tuples and parameter list.

I'm talking about the distinction it claims exists between the latter two.

disconcision 7 hours ago | parent | next [-]

all three are isomorphic. but in some languages if you define a function via something like `function myFun(x: Int, y: Bool) = ...` and also have some value `let a: (Int, Bool) = (1, true)` it doesn't mean you can call `myFun(a)`. because a parameter list is treated by the language as a different kind of construct than a tuple.

antonvs 7 hours ago | parent | prev [-]

A language which truly treats an argument list as a tuple can support this:

    args = (a, b, c)
    f args
…and that will have the effect of binding a, b, and c as arguments in the called function.

In fact many “scripting” languages, like Javascript and Python, support something close to this using their array type. If you squint, you can see them as languages whose functions take a single argument that is equivalent to an array. At an internal implementation level this equivalence can be messy, though.

Lower level languages like C and Rust tend not to support this.

Pay08 5 hours ago | parent [-]

Rust definitely should. C++s std::initializer_list is a great tool and you wouldn't need macros for variadic functions anymore.

naasking 6 hours ago | parent | prev [-]

Presumably creating a different class for parameter lists allows you to extend it with operations that aren't natural to tuples, like named arguments.