[Pitch] `Never` as the parameter type for function that does not take a parameter

Developers familiar with functional languages are a distinct minority, and those with a computer science background even more so.

I, for one, would not have known what a Unit type was without explanation, and my undergrad was in CS.

4 Likes

Sure, but Kotlin also uses Unit and people seem to learn quickly. In fact, many newer languages spell it Unit. Some languages even spell it NUL or struct{}. Python uses None. People seem to cope.

But I digress even more.

3 Likes

I think the logic still kind of fits, though. You have a function that takes no parameters. Since it is impossible to pass any parameters to it, you represent that by passing an impossible value.

Swift doesn’t model functions as maps from tuples to tuples (anymore), but if it did, functions declared func foo() -> T would map Void to T, not Never to T.

3 Likes

But "passing an impossible value" already has a definition in Swift: it means "this function is not callable".

5 Likes

It sounds like that would also give you a way to construct a value of type Never. Inside your bar function arg would contain the value that cannot be created. That sounds like it would break the concept of Never.

5 Likes

Looking at the motivating example, to avoid syntax artists feeling the need to provide beautification overloads like this:

I would say that, today, you probably just shouldn't do this at all—overloading in Swift is best avoided, because it leads to slow compile times and confusion with generics. But in the fullness of time, this is something that variadic generics would address, since you could define the original protocol as:

struct PassthroughSubject<Output...> {
  func send(_ outputs: Output...)
}

and that would let you call it as send() for no arguments, send(x) for one argument, or send(x, y) for multiple arguments, without micromanaging the overload set.

17 Likes

send() and send(_:) have different arities; are they actually overloads?

They are and they aren't, since we never fully committed to the "arity and labels are part of the name" model. It's less bad than overloading the same arity/labels, but we still have a lot of places where an unapplied reference like foo.send could resolve to either.

7 Likes

For a concrete example, this is why we don’t have count(where:) in the standard library.

1 Like

This works well for a verb (i.e. something that historically has () after it) but not for other parts of speech/concepts which, over time, lose their vestigial parens.

There's still one more level of syntactical nothingness to eliminate in those cases, e.g.

extension Result where Success == Void {
  static var success: Self { .success(()) }
}

That's a pain nowadays not only because of having to write the overloads (and deal with any compilation speed hits), but also the inconsistency of the compiler's interpretation of them:

// In most contexts, this `result` is now `.success(())`
let result = Result<_, Error>.success

// And you can get the one that nobody probably wants with explicit typing.
let makeResult = Result<_, Error>.success as (()) -> _

// But here…
extension Result where Success == Void {
  static func voidSuccessContext() {
    // Currently, "Ambiguous use of 'success'", without explicit typing to `Self`.
    let result = success
  }
}

I don't think variadic generics address this?

if we aren’t already committed to the “arity and labels are part of the name” model, we should commit to it now, otherwise documentation tooling maintainers are in for a world of pain…

This seems like the sort of pitch that is Never going to get much traction..

(ba dum tiss)

5 Likes

I feel you may not have passed an argument there.

/groan

2 Likes

It may be both useful and make sense. Maybe it also fits some logic. But that's besides the point: It is impossible to use Never for this use case today, because Never already have a difference, non-compatible meaning. Changing its semantics would break the existing semantics.

If this is indeed deemed useful, a new type would need to be invented. Maybe Nothing or something like that. Feel free to suggest and bike shed names.

But Never is never going to get repurposed as proposed.

3 Likes

If SE-0347 gets accepted we could perhaps also use default arguments?

struct PassthroughSubject<Output> {
  func send(_ output: Output = ())
}
2 Likes

That was my initial understanding of SE-0347 initially. Then I changed my mind.

Changed your mind, in the sense that you consider a function definition like that to be harmful, or at least bad practice? Or you changed your mind, in the sense that you no longer think such a function definition should compile under SE-0347?

As I tried to explain in the linked comment, I think that using SE-0347 in order to solve this kind of problem is ill-advised.

To say it in another way: it is not because you find ONE type that is a possible default, that you found THE type that is designed to be the default (the real use case of SE-0347).

In my Void/Ping example, using SE-0347 would have a different outcomes, depending on it is Void, or Ping, that is the first type that is identified as a sensible default. If the evolution of code has Ping appear first, then SE-0347 would use Ping. If the evolution of the same code has Void appear first, then SE-0347 would use Void. To me, this is the sign that SE-0347 was improperly used in this case. Void is not designed to be the default type. It just happens that one developer was first to think that Void would be a good default. This developer was just lucky. In reality he's squatting SE-0347, he's using an undue convenience.

Thanks. Then I did understand you correctly :-)

2 Likes