More consistent function types

Slightly off-topic, but it's weird that we do not allow named single element tuples. Could come handy sometimes as nice syntactic sugar.

var content: (size: CGSize) { ... }
var content: (offset: CGPoint) { ... }

// use side
content.size.width
content.offset.x
2 Likes

Yes, this is not productive. Lets conclude on where each of us stand. I am not asking for anything about the general case. I am just supporting Mark's suggestion:

An I acknowledge your point:

Exactly.

The only difference between us seems to be that you don't like the idea of abolishing the concept of a function with zero arguments, while I do like the idea, because:

In my opinion, the difference between having no arguments with having an argument that can only ever have a single value of empty (or nothing) is not significant enough to warrant keeping both concepts around.

1 Like

This is not the basis on which one may justify a source-breaking change.

My understanding was that the distinction of zero-arg vs Void-arg was not being enforced till Swift 4.1 and the source breaking nature of this enforcement was mentioned as the motivating factor for this pitch to avoid breaking existing source code.

This proposal is source-breaking where it treats two currently distinct overloads as the same (and therefore a compile-time error).

Does this mean that this is about the distinction between a pre-approved source breaking change enforced in Swift 4.1 vs a change that voids the approved breakage but introduces a different mode of source breakage?

If that is the case, we may need to look into which source breakage has the more actual impact on the existing code.

For me personally: The approved breakage does break some of my source code, but Mark's proposal does not actually break any of my existing source code. So, I am still in favor of the pitch.

Source-breaking proposals need extreme justification. We don't break source because there's an opinion that keeping something around isn't important.

OK, fair enough. Thank for being so responsive and patient with me.

(hoping to not get too off topic here)

Do you know why this is? It seems like it would be useful, for the same reason that sole parameters to a function can have a label -- it clarifies the value's role. Most of the time this isn't needed, but it would help a lot for @discardableResult functions, where the result's role needs some explanation. The call-site can even opt-in to the annotation (but let's ignore that asymmetry here).

(Also, it would benefit the Regex<T> strawman which uses tuple labels to communicate capture names, as named single-capture regexes otherwise couldn't use this technique)

Can I suggest starting a separate thread for this topic so that this one doesn't get hijacked?

I don't actually know why that restriction is in place but it would be interesting to know the history of that choice and whether it still makes sense (unnamed single-element tuples would definitely result in ambiguities, so perhaps the restriction is there so that we consistently disallow single element tuples of all kinds).

1 Like

Spun off at Single-element labeled tuples?

1 Like

Now I’m thinking of something an old math teacher told me about an answer to a math-competition question. The answer was the empty set. The student wrote that as {∅}. The judges rejected it as a set of one element (which was itself a set, the empty set). I guess you’re siding with the student, while I agree with the judges.

3 Likes

FWIW, I know the difference between an empty set and a set containing an empty set. (Being married to a peano player, ahem.) It’s just that for a casual language user like me it’s sort of non-intuitive that I can’t use generics to arrive from (T) -> () to () -> () by using Void for T. Even though it’s obvious now that I know that Void is a typealias for an empty tuple. I guess the moral of the story is to be a bit more careful before posting to Evolution as a casual language user :–)

1 Like

After re-reading the proposal which suggested removing tuple splat and looking at your example here:

I have to agree with @xwu. This pitch would definitely seem like a request to reinstate tuple splatting. In addition, I think that it would make it more difficult to reason about generic functions as well. For example, if I have a functions () -> U and (T) -> U, I don't know why I should expect to be able to use T in a function that has no arguments, and conversely , why I should expect not to use T in a function that I have defined as taking it in as an argument (T) -> U. Isn't this part of why we have type safety in Swift? So that we may easily reason which types are where and not reach a point where we have been handed a type that is unexpected? It seems like allowing () -> U to "equal" (T) -> U is an opening for complexity just for the sake of not having to explicitly write () -> U and (T) -> U, but instead be allowed to write (T) -> U alone, intend to mean both, and even intend to mean (A, B, ... ) -> U as well. I'm just a hobbyist when it comes to programming so please forgive me if I have said anything to offend, certainly not intended. I recall there being a lot of heated discussion on this topic in the past. I am just trying to understand how this would help the language beyond syntactic sugar.

1 Like

No. I don't know why I can't properly communicate what I am advocating.

It appears that functions have a minimum of zero and a maximum of one return value. But in reality functions have a minimum of one and maximum of one (exactly one) return value.

On the other hand, functions can have zero or more arguments; a mix of simple arguments, inout argument and/or vararg argument. This proposal is about changing the rule for arguments so that each functions have one or more arguments.

"No return value" for a function actually means a return type of Void and a fixed return value of () (the sole value of type Void).

This proposal want to make "No input argument" to actually mean one input argument of type Void with the argument value of ().

The above two are considered more consistent (both starting at one, rather than permitting really no input argument, but not permitting really no returns argument).

I quote Mark again:

This has nothing to do with splatting.

A signature like this:

func f<T>() -> () -> U

would be sugar for:

func f<T>(_: Void = ()/*[1]*/ ) -> (Void) -> T
// [1]: Look at this as the sole value of type Void. It just happens to be an empty tuple

Just as:

func f<T>(_x: T)

is a sugar for:

func f<T>(_ x: T) -> Void

Is there any tuple splats here?

Yes, it's precisely equivalent to splatting: you make a single argument that's a tuple of n elements equivalent to a list of n arguments. In this case, it's about specifically n = 0.

OK, I admit it is one way of looking at it and I guessed someone would bring that up. That is why I put this note:

But what you say is also valid and a different way of looking at it.

Yes, and in the same vein, the current convention for result types is precisely equivalent to unsplatting. You declare a function with no return value, and we return a tuple of zero elements, which can be passed to a function that accepts such a type.

1 Like

Would this remove the zero-vs-one-argument duplication from here?

If so, big +1 from me. If not, normal-sized +1 from me.

Unless I am missing something, this is a one-vs-two-argument difference, so no.

(I won't hijack the thread by giving details on how I think with extra runtime overhead you can remove the duplication, but we can discuss in Users or somewhere else if you'd like).