The code below compiles and runs OK, and seems to indicate that the closure and String.init(describing:) functions are completely equivalent in their signature, since .map method happily takes both of them.
let someDict: [String: String] = [
"string1" : "Hello",
"string2" : "Bye",
]
//One way to call .map
var closure = { (key: String, value: String) -> String in
return "The key is \(key), the value is \(value)"
}
someDict.map(closure)
//Another way to call .map
someDict.map(String.init(describing:))
But how is it possible to place into .map a String.init(describing:) function which is a function of only 1 argument, while .map expects a function of 2 arguments? Or am i misunderstanding something here..
Btw, checking the documentation shows that it really does expect a function of 2 arguments:
transform: ((key: String, value: String)) throws -> T
Btw, checking the documentation shows that it really does expect a
function of 2 arguments:
transform: ((key: String, value: String)) throws -> T
Actually, no. Notice the extra parentheses (). It shows that it expects a function that takes one argument which is a tuple containing two elements.
Consider this example:
// function foo takes two arguments
func foo(_ a: Int, _ b: Int) -> Int {
return a + b
}
// function bar takes one tuple with two elements
func bar(_ a: (Int, Int)) -> Int {
return a.0 + a.1
}
let f1 = foo
print(type(of: f1)) // (Int, Int) -> Int
let f2 = bar
print(type(of: f2)) // ((Int, Int)) -> Int
So, the extra parentheses tell us that map is expecting one argument that is a tuple containing two elements.
The closure passed to map always operates on a single element from the sequence at a time. That element can be a tuple such as your case, and then your closure can deconstruct that tuple into multiple values.
Consider this example:
// tup is a tuple containing 3 values
let tup = (1, true, "hello")
// deconstruct the tuple through assignment
let (x, y, z) = tup
print(x) // 1
print(y) // true
print(z) // hello
So in this example:
var closure = { (key: String, value: String) -> String in
return "The key is \(key), the value is \(value)"
}
someDict.map(closure)
map's closure is given a tuple of the form (key: String, value: String) and the closure is deconstructing that into key and value just as the let did above.
In this example:
someDict.map(String.init(describing:))
which is equivalent to:
someDict.map({ String(describing: $0) })
map is taking the whole tuple and passing it to String(describing:).
Related
In swift, you can pass functions as parameters to functions accepting closures. This is particularly useful to avoid syntactically polluting your code when using operators. For instance, you can write a sum as follows:
let values = 0 ..< 10
let sum = values.reduce(0, +)
Unfortunately, overloaded functions can lead to ambiguous situations when Swift’s inference is unable to determine the type of the expected closure from other arguments. Consider the code below for instance. The last line does not compile because Swift cannot decide what “version” of + I am referring to.
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// The following line cannot compile.
let x = castAndCombine((1, 2), with: +)
Unfortunately, there isn’t (or at least I am not aware of) any way to specify which + I mean. Nonetheless, I came up with two solutions to this problem:
Add a parameter to the function to disambiguate the situation:
func castAndCombine<T, U>(_ pair: (Any, Any), toType: T.Type, with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), toType: Int.self, with: +)
Leave the function’s signature unchanged and use a closure with explicit type annotations:
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), with: { (a: Int, b: Int) in a + b })
I personally dislike the first solution, as I feel it is not aesthetic and unnatural to use. However, I wonder if the second one adds any performance overhead, due to the creation of a closure that essentially wraps a single function, without adding any behavior.
Does anyone know if this performance overhead does actually exist and/or is significant to any extent?
There should not be any overhead if you compile with optimizations, as the compiler will most likely inline your closure.
You can verify this assumption with your first solution (as it supports both styles) by comparing the LLVM code Swift writes. LLVM is an intermediate representation used by the compiler right before creating actual machine code.
Write one file using the operator directly, i.e.:
let x = castAndCombine((1, 2), toType: Int.self, with: +)
Write a second file using the closure, i.e.:
let x = castAndCombine((1, 2), toType: Int.self, with: { (a: Int, b: Int) in a + b })
Now compile both with optimizations, asking Swift's compiler to produce the LLVM IR. Assuming your files are named main1.swift and main2.swift, you can run the following:
swift -O -emit-ir main1.swift 1>main1.ll
swift -O -emit-ir main2.swift 1>main2.ll
Both produced files should be identical.
diff main1.ll main2.ll
# No output
Note that the solutions suggested in the comments do not add any performance overhead either, as statically guaranteed casts do not cost any operation.
Instead of creating a closure to disambiguate the type, you can cast + to the desired type:
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// Add two Ints by concatenating them as Strings
func +(_ lhs: Int, _ rhs: Int) -> String {
return "\(lhs)\(rhs)"
}
if let x = castAndCombine((1, 2), with: (+) as (Int, Int) -> String) {
print(x)
}
12
if let x = castAndCombine((1, 2), with: (+) as (Int, Int) -> Int) {
print(x)
}
3
The following piece of code are erroneous in Swift.
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {$0}))
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {return $0}))
The error given by XCode playground is Cannot convert value of type '(Int, Int)' to closure result type 'Int'.
While the following pieces of code are completely fine.
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {$0 + $1}))
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {$1; return $0}))
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {a, b in a}))
It seems that in a situation where arguments to a closure are referred to by shorthand argument names, they must be used exhaustively if the the body of the closure only consists of the return expression. Why?
If you just use $0, the closure arguments are assumed to be a tuple instead of multiple variables $0, $1 etc. So you should be able to work around this by extracting the first value of that tuple:
print(foo(closure: {$0.0}))
Your "why" is like asking "why is an American football field 100 yards long?" It's because those are the rules. An anonymous function body that takes parameters must explicitly acknowledge all parameters. It can do this in any of three ways:
Represent them using $0, $1, ... notation.
Represent them using parameter names in an in line.
Explicitly discard them by using _ in an in line.
So, let's take a much simpler example than yours:
func f(_ ff:(Int)->(Void)) {}
As you can see, the function f takes one parameter, which is a function taking one parameter.
Well then, let's try handing some anonymous functions to f.
This is legal because we name the parameter in an in line:
f {
myParam in
}
And this is legal because we accept the parameter using $0 notation:
f {
$0
}
And this is legal because we explicitly throw away the parameter using _ in the in line:
f {
_ in
}
But this is not legal:
f {
1 // error: contextual type for closure argument list expects 1 argument,
// which cannot be implicitly ignored
}
The following code is fine:
func intTest() -> Int? {
var res : Int
res = 5
return res
}
There is no problem returning a non-optional Int from a method with a return type of optional Int.
Now lets see what happens when the return value is a tuple of optional values:
func tupleTest() -> (Int?, Int?) {
var res : (Int, Int)
res = (5, 5)
return res
}
The return res line results in the error:
error: cannot express tuple conversion '(Int, Int)' to '(Int?, Int?)' (aka '(Optional, Optional)')
Why does Swift not allow this?
I certainly understand why this would be an error in the other direction (optional to non-optional) but why can't a tuple accept a non-optional value in the place of an optional when a non-tuple works fine?
I've seen Swift tuple to Optional assignment but that doesn't cover why you can't do the assignment, it just covers how to solve it.
The reason that's happening is because of a type mismatch. Consider the following case:
let myClosure: (Int) -> ()
let myOtherClosure: (Int?) -> ()
The type of myClosure is (Int) -> () while myOtherClosure is of type (Int?) -> () which makes them fundamentally different types, despite the similarity of the parameters. When Swift is looking at these two constants, it's evaluating the type as a whole, not the individual pieces. The same is happening with your tuple; it's looking at the tuple type signature as a whole unit, not breaking down the parameters and recognizing that they're non-optional versions of the same type.
Converting from Int to Int? works because they're the same type, one is just an optional. Going from (Int, Int) to (Int?, Int?) doesn't because the parameters of the tuple are different therefore causing the overall type to be different, hence your error.
Taking a look at one of your examples:
func tupleTest() -> (Int?, Int?) {
let first: Int = 1
let second: Int = 2
let res: (Int?, Int?) = (first, second)
return res
}
This works because even though the values are non-optional integers, the tuple type is marked as (Int?, Int?) whereas:
func tupleTest() -> (Int?, Int?) {
let first: Int = 1
let second: Int = 2
let res = (first, second)
return res
}
doesn't compile because now res is of type (Int, Int). I'm not an expert on the Swift compiler but my guess is that the way the type system works is it doesn't deconstruct each individual part of a function or a tuple and recognize that the corresponding return parameter is the same type, just an optional.
I am getting a compile time error that myFunc reference is ambiguous.
func f (s: String) -> String { return "version 1: " + s }
func f(sourceString s: String) -> String { return "version 2: " + s }
var myFunc: (String)-> String = f as (sourceString : String)->String
How can I explicitly reference each version of the overloaded function, f, in the example above? If I comment out either declaration of func f it will compile and work. But I would like to know how to reference each of the functions if both are declared. Thanks.
I don't know how to do exactly what you want, but maybe this helps:
var myFunc1: (String)-> String = { s in f(sourceString: s) }
var myFunc2: (String)-> String = { s in f(s) }
You can now call:
let s1 = myFunc1("one") // returns "version 2: one"
let s2 = myFunc2("two") // returns "version 1: two"
Interesting one this. I don’t think it’s possible without doing something along the lines of #marcos’s suggestion. The problem you is you can “cast away” the names in tuples:
let named_pair = (s: "hello", i: 1)
named_pair.s // hello
let anon_pair = named_pair as (String,Int)
// or anon_pair: (String,Int) = named_pair, if you prefer
anon_pair.s // no such member 's'
Now suppose you define two functions, identical except one has named arguments:
func f(s: String, i: Int) { println("_: \(s)") }
func f(#s: String, #i: Int) { println("s: \(s)") }
You can then call it via tuples with named vs unnamed arguments:
f(named_pair) // prints s: hello
f(anon_pair) // prints _: hello
// but if you try to call a named argument function with unnamed tuples:
func g(# s: String, # i: Int) { println("s: \(s)") }
g(anon_pair) // compiler error
let h = g
h(anon_pair) // compiler error
h(named_pair) // works
But because you can cast away these names you can do this:
// compiles and runs just fine...
(g as (String,Int)->())(anon_pair)
let k: (String,Int)->() = g
// as does this
k(anon_pair)
And this ability to do this means it’s not possible to use a type to disambiguate an function overloaded only by argument names, as far as I can tell.
Referencing func f (s: String) -> String { return "version 1: " + s }:
let myFunction = f(s:)
Referencing func f(sourceString s: String) -> String { return "version 2: " + s }:
let myFunction = f(sourceString:)
Referencing func anotherFunction(_ param: Any) {}:
let myFunction = anotherFunction(_:)
If you haven't overloaded the function, you don't need to explicity write out the parameter names when referencing the function.
Number of arguments should vary.
If the number of arguments are same then their data types should
vary.
Example
func f(x : String) -> NSString {
return a
}
func f(x : UInt) -> NSString {
return "{\(x)}"
}
I don't think you can. You can call one or the other:
println(f("test")) // version 1: test
println(f(sourceString: "test")) // version 2: test
Is it possible to pass in a tuple into a function as long as their types match up?
When I try it, I get a missing argument in parameter error:
var myTuple = ("Text",10,"More Text")
func myFunction(a:String, b:Int, c:String) {
// etc...
}
myFunction(myTuple)
It was possible, although was deprecated in Swift 2.2:
In Swift 2.1 and earlier it was possible to use a carefully crafted tuple to fill the parameters of a function. So, if you had a function that took two parameters, you could call it with a two-element tuple as long as the tuple had the correct types and element names.
...
This syntax — affectionately called “tuple splat syntax” — is the antithesis of idiomatic Swift’s self-documenting, readable style, and so it’s deprecated in Swift 2.2.
https://swift.org/blog/swift-2-2-new-features/
I came here wanting to know how to pass a tuple as a function parameter. The answers here focus on a different case. I'm not entirely clear what the OP was after.
In any case, here is how to pass a tuple as a parameter. And, for good measure, how to do it variadically.
func acceptTuple(tuple : (Int, String)) {
print("The Int is: \(tuple.0)")
print("The String is '\(tuple.1)'")
}
acceptTuple((45, "zebras"))
// Outputs:
// The Int is: 45
// The String is 'zebras'
func acceptTuples(tuples : (Int, String) ...) {
var index = 0
// note: you can't use the (index, tuple) pattern in the for loop,
// the compiler thinks you're trying to unpack the tuple, hence
/// use of a manual index
for tuple in tuples {
print("[\(index)] - Int is: \(tuple.0)")
print("[\(index)] - String is '\(tuple.1)'")
index++
}
}
acceptTuples((45, "zebras"), (17, "armadillos"), (12, "caterpillars"))
//Outputs
//[0] - Int is: 45
//[0] - String is 'zebras'
//[1] - Int is: 17
//[1] - String is 'armadillos'
//[2] - Int is: 12
//[2] - String is 'caterpillars'
Passing tuples in can be a quick and convenient approach, saving you from having to create wrappers etc. For example, I have a use case where I am passing a set of tokens and parameters to create a game level. Tuples makes this nice and compact:
// function signature
class func makeLevel(target: String, tokens: (TokenType, String)...) -> GameLevel
// The function is in the class Level. TokenType here is an Enum.
// example use:
let level = Level("Zoo Station", tokens:
(.Label, "Zebra"),
(.Bat, "LeftShape"),
(.RayTube, "HighPowered"),
(.Bat, "RightShape"),
(.GravityWell, "4"),
(.Accelerator, "Alpha"))
Yes, it's possible under these conditions:
the tuple must be immutable
the number of values in the tuple, their type, and their order must match the parameters expected by the function
named parameters must match external names in the function signature
non-named parameters must match parameters without external name in the function signature
So, your code is ok, the only thing you have to do is turning the tuple into an immutable one (i.e. using let and not var):
let myTuple = ("Text", 10, "More Text")
func myFunction(a:String, b:Int, c:String) {
// etc...
}
myFunction(myTuple)
One more example with external names:
let myTuple = ("Text", paramB: 10, paramC: "More Text")
func myFunction(a:String, paramB b:Int, paramC c:String) {
// etc...
}
myFunction(myTuple)
In your tuple, it appears as though you must name them and then refer to them as such:
so your code should be
var myTuple = (val1: "Text", val2: 10, val3: "More Text")
func myFunction(a:String, b:Int, c:String) {
// etc...
}
myFunction(myTuple.val1, myTuple.val2, myTuple.val3)
The tuple has named values (val1, val2, val3) which you set and then reference, when you pass in myTuple, to the function myFunction(), it appears as though you are just filling 1 of the 3 available arguements - and with the wrong type to boot! This is the equivalent of storing the types in a tuple, then taking them out for a function call. However, if you want a function to actually take a tuple as a parameter, see below:
var myTuple = (val1: "Text", val2: 10, val3: "More Text")
func tupleFunc(a:(String, Int, String)) {
}
tupleFunc(myTuple)
Yes, but that's the wrong structure: you're passing three variables called a, b, and c rather than a tuple with those components.
You need parentheses around the whole thing:
var myTuple = ("Text", 10, "More Text")
func myFunction(a:(x: String, y: Int, z: String)) {
println(a)
}
myFunction(myTuple)
You can use the following feature: Swift allows you to pass a function (f1) with any number of parameters (but without inout parameters) as a parameter of type (TIn) -> TOut to another function. In this case, TIn will represent a tuple from the parameters of the function f1:
precedencegroup ApplyArgumentPrecedence {
higherThan: BitwiseShiftPrecedence
}
infix operator <- :ApplyArgumentPrecedence
func <-<TIn, TOut>(f: ((TIn) -> TOut), arg: TIn) -> TOut {
return f(arg)
}
func sum(_ a: Int, _ b: Int) -> Int {
return a + b
}
print(sum <- (40, 2))
In swift 3.0, we should not able to pass the tuple directly to the function.If we did so, it shows the error message as "This type has been removed in swift 3.0"
func sum(x: Int, y: Int) -> Int
return x+y }
let params = (x: 1, y: 1)
let x = params.0
let y = params.1
sum(x: x, y: y)
Hope it helps you!!
The best option for now seems to be to just save it to a compound variable or use the build in dot syntax
let (val1, val2) = (1, 2)
func f(first: Int, second: Int) { }
f(first: val1, second: val2)
let vals = (1, 2)
f(first: vals.0, second: vals.1)
That feature called implicit tuple splat was removed in swift 3.
You can find more detailed explanation on the removal proposal here
Some suggestions to keep using tuple as an argument is by doing so:
func f1(_ a : (Int, Int)) { ... }
let x = (1, 2)
f1(x)
func f2<T>(_ a : T) -> T { ... }
let x = (1, 2)
f2(x)