(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)
(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)
(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)
(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)
(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)
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(())
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?
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) ¯\_(ツ)_/¯
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
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'
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.
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.
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.