Использование init() в map()

TL; DR

Почему это не работает?

"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context

Подробнее

Одна из самых классных вещей, которые мне нравятся в Swift, - это возможность конвертировать коллекцию одной вещи в другую, передавая метод init (предполагая, что существует init() для этого типа).

Здесь приведен пример преобразования списка кортежей в экземпляры ClosedInterval.

[(1,3), (3,4), (4,5)].map(ClosedInterval.init)

В этом примере также используется тот факт, что мы можем передать кортеж аргументов как один аргумент, если кортеж соответствует списку аргументов функции.

Вот еще один пример, на этот раз преобразование списка чисел в строковые экземпляры.

(1...100).map(String.init)

К сожалению, следующий пример не работает. Здесь я пытаюсь разделить строку на список односимвольных строк.

"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context

map() должен работать в списке Character (и действительно, я смог проверить на игровой площадке, что Swift указывает правильный тип [Character], который передается в map).

String определенно может быть создан из Character.

let a: Character = "a"
String(a) // this works

И интересно, это работает, если каждый из символов находится в своем собственном массиве.

"abcdefg".characters.map { [$0] }.map(String.init)

Или эквивалент:

let cx2: [[Character]] = [["a"], ["b"], ["c"], ["d"]]
cx2.map(String.init)

Я знаю, что могу это сделать:

"abcdefg".characters.map { String($0) }

Но я специально пытаюсь понять, почему "abcdefg".characters.map(String.init) не работает (IMO этот синтаксис также более читабельный и элегантный)

Ответ 1

Упрощенное воспроизведение:

String.init as Character -> String
// error: type of expression is ambiguous without more context

Это потому, что String имеет два инициализатора, которые принимают один Character:

init(_ c: Character)
init(stringInterpolationSegment expr: Character)

Насколько я знаю, нет способа устранить их при использовании инициализатора в качестве значения.

Что касается (1...100).map(String.init), String.init называется Int -> String. Хотя есть два инициализатора, которые принимают один Int:

init(stringInterpolationSegment expr: Int)
init<T : _SignedIntegerType>(_ v: T)

Общий тип слабее, чем явный тип. Поэтому в этом случае компилятор выбирает stringInterpolationSegment:. Вы можете подтвердить, что нажатием command + нажмите .init.

Ответ 2

"abcdefg".characters.map(String.init) отлично работает с Xcode 10. Нет ошибки компиляции.

@rintaro Любые документы/статьи, касающиеся инициализатора как значения.

Я думаю,

public init (stringInterpolationSegment expr: T) доступен в строке. Так что String.init ссылается на это.