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.
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) }
.