Reputation: 794
The print
function can print integers and can be called like print(5)
. I want to do the same on an [Int]
array.
However attempting to do the same on a forEach
fails.
[1,2,3].forEach(print)
gives
expression failed to parse:
error: repl.swift:20:17: error: cannot convert value of type
'(Any..., String, String) -> ()' to expected argument type
'(Int) throws -> Void'
[1,2,3].forEach(print)
^
How can I get Swift to perform this conversion from (Any, ... String, String) -> ()
to (Int) throws -> Void
without resorting to the workarounds listed below?
Aren't function types contravariant in the parameter position, meaning that since Any
is a supertype of Int
, (Any, ... String, String) -> ()
is a subtype of (Int) throws -> Void
(since ()
is the same as Void
) and therefore by the Liskov substitution principle, print
should be a perfectly acceptable argument to forEach
in this case?
Is it an issue with the number of (variadic AND optional!) parameters in (Any, ... String, String)
?
These work but they seem unnecessary to me.
Creating an inline closure using shorthand argument name [1,2,3].forEach { print($0) }
or defining a function func printInt(_ n: Int) { print(n) }; [1,2,3].forEach(printInt)
both work.
This suggests that the issue is at least partly with the number of arguments. However I am not clearly understanding what is happening here and would appreciate any help.
Upvotes: 1
Views: 97
Reputation: 273310
Is it an issue with the number of (variadic AND optional!) parameters in
(Any, ... String, String)
?
Yes. There is no syntax for function type parameters that say "this is an optional parameter". When you treat print
as a first class object, its optional parameters automatically becomes required.
let p = print
// p's type is (Any..., String, String) -> Void
func notOptionalPrint(_ items: Any..., separator: String, terminator: String) { ... }
let q = notOptionalPrint
// q's type is also (Any..., String, String) -> Void
Furthermore, the type of the first parameter is Any...
. This is not the same as Any
. It is not a supertype of Int
or Any
, so LSP does not apply here, even if the optional parameters problem is magically solved.
Therefore, you have to wrap it in some way if you want to pass it directly to forEach
. Rather than wrapping it just for Int
, you can consider wrapping it for Any
:
func print(_ x: Any) {
// need "Swift." here, otherwise infinite recursion
Swift.print(x)
}
Then, because of LSP, you can pass this directly to forEach
for collections of any element type.
Personally though, I wouldn't bother, and just use forEach { print($0) }
.
Upvotes: 1
Reputation: 3094
This is another workaround if you only care about using print
:
extension Sequence {
func printEach() {
for element in self {
print(element)
}
}
}
[1,2,3].printEach()
Or a variation of your printInt
workaround that works with any type:
func printElement(_ element: Any) {
print(element)
}
[1,2,3].forEach(printElement)
Neither are very appealing to me and just using the print($0)
closure is likely to be clearer to people reading the code.
Upvotes: 1