Edit: I deleted a bunch of stuff because I was getting answers about something other than the title.
Given all this:
func ƒ0(_: () -> Void) { }
func ƒT<T>(_: (T) -> Void) { }
func ƒ00(_: () -> () -> Void) { }
func ƒ0T<T>(_: () -> (T) -> Void) { }
func makeClosure() -> () -> Void { { } }
func makeWeirdClosure() -> (()) -> Void { { _ in } }
Why do these compile…
ƒ0 { }
ƒT { }
ƒ00 { { } }
ƒ00(makeClosure)
ƒ0T(makeWeirdClosure)
…but not these?
ƒ0T { { } }
ƒ0T(makeClosure)
Is there no way to avoid extra-tupling with curried inputs?
sveinhal
(Svein Halvor Halvorsen)
10
I would really like for Swift to just automatically allow a function f(Void) -> T to be callable as f() instead of f(()). This is truly annoying when using generics. I have no idea how often I've had to write stuff like this:
public extension Result where Success == Void {
static var success: Result { return .success(()) }
}
Just so I can write return .success instead of return .success(())
Or this (which also deals with the fact that Void isn't hashable):
public enum Unit: Hashable { case unit }
public extension Cache where Key == Unit {
typealias VoidGenerator = () -> Value
convenience init(_ generator: @escaping VoidGenerator, maxAge: DispatchTimeInterval = .never) {
self.init(PromiseCache.lift(generator), maxAge: maxAge)
}
func getIfNeeded(forceReload: Bool = false) -> Value {
return getIfNeeded(.unit, forceReload: forceReload)
}
var cachedValue: Value? {
return self[.unit]
}
func purge() {
purge(.unit)
}
private static func lift(_ generator: @escaping VoidGenerator) -> Generator {
return { (_: Unit) in generator() }
}
}
Just to allow me to use Void as a "key" even though it's not hashable, and then allow me to write .getIfNeeded() instead of .getIfNeeded(.unit) or .getIfNeeded(())
1 Like
Jens
11
Wouldn't that have strange implications or at least lead to questions like these:
protocol P {
associatedtype A
func foo(_ v: A) -> A
}
struct S: P {
func foo() { print(A.self) } // Should this satisfy P's requirement and print Void?
}
func foo<T>(_ a: T, _ b: T) -> (T, T) { return (a, b) }
foo() // Should this compile and print ((), ())?
typealias A = () -> Void
typealias B = (Void) -> Void
typealias C<T> = (T) -> Void
print(A.self) // () -> ()
print(B.self) // (()) -> () but should be () -> ()?
print(C<Void>.self) // (()) -> () but should be () -> ()?
print(A.self == B.self) // false, but should be true?
print(A.self == C<Void>.self) // false, but should be true?
print(B.self == C<Void>.self) // true, but should be false?
sveinhal
(Svein Halvor Halvorsen)
12
Probably. I don't know. As per usual there is probably some reason I can't have what I want. (And a reason I'm not a compiler engineer or language designer) ¯\_(ツ)_/¯
sveinhal
(Svein Halvor Halvorsen)
13
Don't we have similar ambiguities elsewhere in the language? E.g we allow for a non-optional to be passed, where an optional is expected. And now to pass a keypath literal where a function is expected. But we still have
Optional<T>.self == T.self // false
((A) -> B).self == KeyPath<A, B>.self // false
Idk. Maybe we could allow for func f(Void) -> () to be a candidate for call site f() but with a lower overload priority score than func f()?
Jens
14
The OP is asking about an inconsistency, and I'd like to focus on the first inconsistency demonstrated by the example.
The type of ƒ0 is:
(( ) -> Void) -> Void
The type of ƒT<Void> is:
((()) -> Void) -> Void
So, ƒ0 takes an argument of type () -> Void, and
ƒT<Void> takes an argument of type (()) -> Void.
Ie, the functions are of different types (because they take arguments of different types), as demonstrated by the following code:
let f: (( ) -> Void) -> Void = ƒ0
let g: ((()) -> Void) -> Void = ƒT
print(type(of: f)) // (() -> ()) -> ()
print(type(of: g)) // ((()) -> ()) -> ()
print(type(of: f) == type(of: g)) // false
let a: ( ) -> Void = { }
let b: (()) -> Void = { _ in }
print(type(of: a)) // () -> ()
print(type(of: b)) // (()) -> ()
print(type(of: a) == type(of: b)) // false
// OK, so f takes a, and g takes b, but look here:
f(a) // Compiles (as expected)
g(b) // Compiles (as expected)
f(b) // ERROR (as expected): Cannot convert value of type '(()) -> Void' to expected argument type '() -> Void'
g(a) // Compiles (... but why?)
I'd expect this:
f(a) // Compiles
g(b) // Compiles
f(b) // ERROR: Cannot convert value of type '(()) -> Void' to expected argument type '() -> Void'
g(a) // ERROR: Cannot convert value of type '() -> Void' to expected argument type '(()) -> Void'
1 Like
xwu
(Xiaodi Wu)
15
2 Likes
Jens
16
So that's the answer to the question of the OP then, that this exception is ... well, an exception
(that thus doesn't apply consistently through the example of the OP).
As is true for all exceptions, they are motivated by usability / ergonomics in some context, but they always pop up as a confusing and/or frustrating inconsistency in some other context.
I wish Swift had fewer exceptions like this.
1 Like
sveinhal
(Svein Halvor Halvorsen)
17
Thankfully, I don't have to write this:
let promise: Promise<Void, Error> = ...
promise.onSuccess { _ in // <- void argument
// do something
}
But can write this instead:
promise.onSuccess {
// do something
}
However, I still would want f() to consider func f(_: Void) as a candidate.
Thanks for digging that up!
Now it's safe to repost the relevant part of the use case. I'd like to avoid having to extend this:
struct WeakMethod<Reference: AnyObject, Input, Output> {
typealias Method = (Reference) -> (Input) -> Output
weak var reference: Reference?
var method: Method
struct ReferenceDeallocatedError: Error { }
/// - Throws: ReferenceDeallocatedError
func callAsFunction(_ input: Input) throws -> Output {
guard let reference = reference
else { throw ReferenceDeallocatedError() }
return method(reference)(input)
}
}
…with this:
extension WeakMethod where Input == () {
init(
reference: Reference?,
method: @escaping (Reference) -> () -> Output
) {
self.reference = reference
self.method = { reference in
{ _ in method(reference)() }
}
}
/// - Throws: ReferenceDeallocatedError
func callAsFunction() throws -> Output {
try self( () )
}
}
Without that extension's initializer, this won't compile:
final class Reference {
func method() { }
}
var reference: Reference? = .init()
let method = WeakMethod(reference: reference, method: Reference.method)
I think it's incorrect for that Input to be () there, but the empty tuple is the closest representation the type system has to nothingness. I can understand why I need the extension method, to allow for this:
try method()
…but the initializer being required comes off as really weird, to me, and the problem didn't come along with a helpful error message, either. I think Svein is onto something. Nested zero-tuples should probably always be condensed to nothingness.
1 Like
xwu
(Xiaodi Wu)
19
This was the case for earlier versions of Swift. It is an explicit design decision of SE-0110 that this not be the case.
…which means that it's impossible to represent
let voidClosure: ( () -> Void ).Type
with any form of this:
typealias Closure<Input> = (Input) -> Void
…even though it works with every other type?
let intClosure: ( (Int) -> Void ).Type = Closure.self
let neverClosure: ( (Never) -> Void ).Type = Closure.self
let horribleClosure: ( ( () ) -> Void ).Type = Closure.self
Jens
21
Yes, the generic parameter represents exactly one type (not zero, not two or more). See somewhat related discussion in this post and replies.
So variadic generics will solve it? 
hooman
(Hooman Mehr)
23
Here is another thread that might be relevant:
If was proposing making
func f<T>() -> T { /*...*/ }
sugar for:
func f<T>(_: Void = () ) -> T { /*...*/ }
During Swift 4.0 to 4.1 transition. It meant functions would have from 1 to n arguments, instead of 0 to n.
The argument for it was reducing source breakage resulting from the removal of tuple splat. I was in favor and didn't really understand what was the valid argument against it.
1 Like