Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using init() in map()

Tags:

swift

swift2

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)

like image 848
vopilif Avatar asked Nov 25 '15 01:11

vopilif


People also ask

How do you initialize a map array?

To initialize a Map with values, use the Map() constructor, passing it an array containing nested arrays of key-value pairs, where the first element in the array is the key and the second - the value. Each key-value pair is added to the new Map .

How do you initialize a map list?

The Static Initializer for a Static HashMap We can also initialize the map using the double-brace syntax: Map<String, String> doubleBraceMap = new HashMap<String, String>() {{ put("key1", "value1"); put("key2", "value2"); }};

How do you initialize an empty map?

For initializing an empty Map, we'll not pass any key-value pair in this method: Map<String, String> emptyMapUsingJava9 = Map. of(); This factory method produces an immutable Map, hence we'll not be able to add, delete or modify any key-value pair.


1 Answers

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.

like image 184
rintaro Avatar answered Sep 21 '22 13:09

rintaro