I have a Swift 3 array that's initialized with the following:
var foo: [String] = []
In one of my methods that gets invoked recursively, I'm attempting to convert a string into an array of characters, but append those characters versus doing a direct assignment to foo. So this will compile:
self.foo = text.characters.map { String($0) }
But the following breaks:
self.foo.append(text.characters.map { String($0) })
The error it produces is: 'map' produces '[T]', not expected contextual result type 'String'
What's the correct way to approach this?
You need to be using the append(contentsOf:) method instead.
foo.append(contentsOf: text.characters.map { String($0) })
This method can take an array of the defined type.
Whereas the append() method expects a single element to add at the end of the array.
Related
Say I call map like this, using the anonymous closure argument $0:
array.map {
return $0.description
}
How would I explicitly define that map returns a string? This doesn’t work:
array.map { -> String
return $0.description
}
Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored
Does that mean if I want to specify a return value I have to name my arguments?
[EDIT: I know I do not need an explicit return type here; still would like how to specify one]
You can use as to identify the type of the anonymous enclosure. In this case you'll need to specify the type of the input as well:
let result = array.map({ $0.description } as (CustomStringConvertible) -> String)
Note: You could use the type of whatever is in array as the input type. Here I just used the CustomStringConvertible protocol since that is what is needed to be able to access the .description property.
or as you mentioned, you can specify the output type if you give a name to the input parameter:
let result = array.map { value -> String in value.description }
Another way to look at it is to note that map returns an Array of whatever type the map closure returns. You could specify that the result of the map is [String] and Swift would then infer that the map closure returns String:
let result = array.map({ $0.description }) as [String]
or
let result: [String] = array.map { $0.description }
The type will be inferred automatically if it's a string , otherwise you need a cast like this
array.map { String($0.description) }
I was trying to define a variable that can hold a closure and ran into some difficulties using Swift's shorthand argument names. Take the following code snippet:
var returnAString: (String, String) -> String
returnAString = { return $0 }
This gives me the compile error '(String, String)' is not convertible to 'String'. When I modify the closure return a string with the first argument value, I get back both argument values in a tuple.
println(returnAString("1", "2")) // Prints "(1, 2)"
However, if I modify the closure to print the second argument the correct argument is printed as expected.
returnAString = { $1 }
println(returnAString("1", "2")) // Prints "2"
I've got the feeling that I'm doing something a little silly here, but I couldn't find any explanation of why this would be happening.
Closures § Shorthand Argument Names
Shorthand Argument Names
…
the number and type of the shorthand argument names will be inferred from the expected function type.
It looks like this is the expected behavior. By providing only $0 you are inferring (at least to the system) that you want a tuple.
This only seems to be a special case of using $0. For example, the following cannot be compiled.
var returnAString3: (String, String, String) -> String
returnAString3 = { return $1 }
No matter how it's modified.
returnAString3 = { return $1.0 } // still fails.
Type inferral is treating $0 as a tuple instead of the first argument, because you are not using the 2nd parameter.
If you just reference the 2nd parameter instead it works correctly:
returnAString = { $1; return $0 }
A proof that what I am saying is correct comes from 0x7fffffff's comment:
returnAString = { $0.0 }
which means take the first value of the $0 tuple and return it.
To the language, "multiple parameters" is the same as a single parameter that is a tuple. Since only $0 was used, and not $1, etc., the compiler first guesses that there is only one parameter. And that guess is always valid because any function type can be viewed to have exactly one parameter (which may be Void, a tuple, or otherwise).
In Swift it seems to be possible to create a variadic function with a vararg parameter which isn't the last parameter of the function.
I created the following function:
func test1(_ array: String..., _ tag: String){
print("[\(tag)] array: \(array)")
}
Because it's possible to define a vararg parameter without making it the last parameter I suspected that the Swift compiler would just take the last argument and make it the argument for the second parameter.
test1("Hello", "World", "Greeting")
But this doesn't work, which is not too strange.
This doesn't work either:
let arrayOfStrings: [String] = ["Hello", "World"]
test1(arrayOfStrings, "Greeting")
Obviously giving the second parameter a label works.
func test2(_ array: String..., tag: String){
print("[\(tag)] array: \(array)")
}
But I decided to make another try. This function compiles doesn't generate a warning, but can I even use it?
func test3(_ array: String..., _ tag: Int){
print("[\(tag)] array: \(array)")
}
test3("Hello", "World", 1)
Is it possible to call the first method or the third method, without giving the second parameter a label?
Side note: Is this a language feature? Shouldn't the compiler have warn me? It currently tells me that I'm missing the second parameter when calling the function, but to me it seems that it's not even possible to provide this parameter.
TL;DR
Why doesn't this work?
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
Details
One really cool thing I like in Swift is the ability to convert a collection of one thing to another by passing in an init method (assuming an init() for that type exists).
Here's an example converting a list of tuples to instances of ClosedInterval.
[(1,3), (3,4), (4,5)].map(ClosedInterval.init)
That example also takes advantage of the fact that we can pass a tuple of arguments as a single argument as long as the tuple matches the function's argument list.
Here another example, this time converting a list of numbers to string instances.
(1...100).map(String.init)
Unfortunately, the next example does not work. Here I am trying to split up a string into a list of single-character strings.
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
map() should be operating on a list of Character (and indeed I was able to verify in a playground that Swift infers the correct type of [Character] here being passed into map).
String definitely can be instantiated from a Character.
let a: Character = "a"
String(a) // this works
And interestingly, this works if the characters are each in their own array.
"abcdefg".characters.map { [$0] }.map(String.init)
Or the equivalent:
let cx2: [[Character]] = [["a"], ["b"], ["c"], ["d"]]
cx2.map(String.init)
I know that I could do this:
"abcdefg".characters.map { String($0) }
But I am specifically trying to understand why "abcdefg".characters.map(String.init) does not work (IMO this syntax is also more readable and elegant)
Simplified repro:
String.init as Character -> String
// error: type of expression is ambiguous without more context
This is because String has two initializers that accept one Character:
init(_ c: Character)
init(stringInterpolationSegment expr: Character)
As far as I know, there is no way to disambiguate them when using the initializer as a value.
As for (1...100).map(String.init), String.init is referred as Int -> String. Although there are two initializers that accept one Int:
init(stringInterpolationSegment expr: Int)
init<T : _SignedIntegerType>(_ v: T)
Generic type is weaker than explicit type. So the compiler choose stringInterpolationSegment: one in this case. You can confirm that by command + click on .init.
I was trying to define a variable that can hold a closure and ran into some difficulties using Swift's shorthand argument names. Take the following code snippet:
var returnAString: (String, String) -> String
returnAString = { return $0 }
This gives me the compile error '(String, String)' is not convertible to 'String'. When I modify the closure return a string with the first argument value, I get back both argument values in a tuple.
println(returnAString("1", "2")) // Prints "(1, 2)"
However, if I modify the closure to print the second argument the correct argument is printed as expected.
returnAString = { $1 }
println(returnAString("1", "2")) // Prints "2"
I've got the feeling that I'm doing something a little silly here, but I couldn't find any explanation of why this would be happening.
Closures § Shorthand Argument Names
Shorthand Argument Names
…
the number and type of the shorthand argument names will be inferred from the expected function type.
It looks like this is the expected behavior. By providing only $0 you are inferring (at least to the system) that you want a tuple.
This only seems to be a special case of using $0. For example, the following cannot be compiled.
var returnAString3: (String, String, String) -> String
returnAString3 = { return $1 }
No matter how it's modified.
returnAString3 = { return $1.0 } // still fails.
Type inferral is treating $0 as a tuple instead of the first argument, because you are not using the 2nd parameter.
If you just reference the 2nd parameter instead it works correctly:
returnAString = { $1; return $0 }
A proof that what I am saying is correct comes from 0x7fffffff's comment:
returnAString = { $0.0 }
which means take the first value of the $0 tuple and return it.
To the language, "multiple parameters" is the same as a single parameter that is a tuple. Since only $0 was used, and not $1, etc., the compiler first guesses that there is only one parameter. And that guess is always valid because any function type can be viewed to have exactly one parameter (which may be Void, a tuple, or otherwise).