ylorn
1
Sample Code:
class Tester<Output, Failure: Swift.Error> {
typealias Execute = (
_ onSuccess: @escaping (Output) -> Void,
_ onFailure: @escaping (Failure) -> Void
) -> Void
let execute: Execute
init(execute: @escaping Execute) {
self.execute = execute
}
}
class Provider {
struct Error: Swift.Error { }
func execute(
onSuccess: @escaping () -> (),
onFailure: @escaping (Error) -> ()
) {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
if Bool.random() {
onSuccess()
} else {
onFailure(.init())
}
}
}
}
let provider = Provider()
let tester: Tester<Void, Provider.Error> = .init(execute: provider.execute)
Following complier error prompts:
Cannot convert value of type '(@escaping () -> (), @escaping (Provider.Error) -> ()) -> ()' to expected argument type '(@escaping (Void) -> Void, @escaping (Provider.Error) -> Void) -> Void'
it seems the complier is expecting a (Void) a.k.a (()) for the onSuccess: part.
Is this expected or a bug? How to get it right if it is not a bug, excepting for changing onSuccess: @escaping () -> () to onSuccess: @escaping (()) -> () in Provider.execute?
Thanks!
Screenshot:
xwu
(Xiaodi Wu)
2
This is expected: your type alias requires that the closure have one parameter of type Output, which you then specify to be Void, and this is not the same as having zero parameters. You will therefore need to provide a closure of type @escaping (()) -> ().
(Fortunately, closures themselves have a special rule such that you can pass as an argument a concrete closure expression which has no parameters.)
2 Likes
sveinhal
(Svein Halvor Halvorsen)
3
I agree that the nested (()) is unwieldy.
1 Like
ylorn
4
Do you mean a special overload like the following extension needs to be added in this case? Like what Combine does for PassthroughSubject with send(Output) and send() when Output is Void? But I remember I read it somewhere that the (()) to () is somewhat automatically supported in Swift?
extension Tester where Output == Void {
typealias VoidOutputExecute = (
_ onSuccess: @escaping () -> Void,
_ onFailure: @escaping (Failure) -> Void
) -> Void
convenience init(execute: @escaping VoidOutputExecute) {
self.init { onSuccess, onFailure in
execute {
onSuccess(())
} _: {
onFailure($0)
}
}
}
}
This is not true. A function/closure with zero arguments is not the same as, nor can it be converted to, a function/closure with a single Void argument.
1 Like
sveinhal
(Svein Halvor Halvorsen)
6
I can for closures. And, in fact, it does.
You can what?
What does it refer to and what does it do?
sveinhal
(Svein Halvor Halvorsen)
8
The "I" was a typo, and should have been "It".
And those "it"s refer to the "it" I quoted from you.
You stated:
A function/closure with zero arguments is not the same as, nor can it be converted to, a function/closure with a single Void argument.
I claim, it can. And in fact, it does.
@sveinhal Give me an example.
sveinhal
(Svein Halvor Halvorsen)
10
I did, in the link above. But I can inline an example for your convenience 
import Combine
let subject = PassthroughSubject<Void, Never>()
func eventHandler() {
print("event triggered")
}
subject.sink(receiveValue: eventHandler)
subject.send(())
Here eventHandler is a function with zero arguments, which is converted to a closure with a single Void argument. It this wasn't the case, you'd have to write it like this:
func eventHandler(_ value: Void) {
print("event triggered \(value)")
}
This is super useful, especially when passing closure literals, and not having to write _ in
subject.sink { _ in // <- not needed, even though it expects a `(Void) -> ()` closure
print("event triggered")
}
2 Likes