Как вы предоставляете аргумент по умолчанию для универсальной функции с ограничениями типа?

#swift #string #compiler-errors

#swift #строка #ошибки компилятора

Вопрос:

Следующее определение функции является законным для Swift:

 func doSomething<T: StringProtocol>(value: T = "abc") {
    // ...
}
  

Компилятор способен определить, что аргументом по умолчанию "abc" является String и String соответствует StringProtocol .

Однако этот код не компилируется:

 func doSomething<T: Collection>(value: T = "abc") where T.Element == Character {
    // ...
}
  

Ошибка компилятора:

Значение аргумента по умолчанию типа ‘String’ не может быть преобразовано в тип ‘T’

Кажется, что компилятор будет располагать таким же количеством информации, как и в первом случае, чтобы определить, что String действительно конвертируемо в T . Более того, если я удалю аргумент по умолчанию и вызову функцию с тем же значением, это сработает:

 doSomething(value: "abc")
  

Можно ли написать эту функцию по-другому, чтобы я мог предоставить аргумент по умолчанию String ? Это ограничение Swift или просто ограничение моей ментальной модели?

Комментарии:

1. Я понятия не имел, что вы можете это сделать; спасибо, что спросили!

Ответ №1:

Существенным ограничением является T: ExpressibleByStringLiteral . Это то, что позволяет инициализировать что-либо из строкового литерала.

 func doSomething<T: Collection>(value: T = "abc")
    where T.Element == Character, T: ExpressibleByStringLiteral {
    // ...
}
  

Как отмечает Лео Дабус, T.Element == Character технически в этом нет необходимости, но его удаление меняет смысл. То, что что-то является коллекцией и может быть инициализировано строковым литералом, не означает, что его элементы являются символами.

Также стоит отметить, что, хотя все это возможно, обычно это плохой Swift IMO. У Swift нет никакого способа выразить, каков тип по умолчанию, поэтому doSomething() во всех этих случаях возникает «Общий параметр ‘T’ не может быть выведен».

Правильное решение IMO — это перегрузка, которая позволяет избежать всех этих проблем:

 func doSomething<T: StringProtocol>(value: T) {
}

func doSomething() {
    doSomething(value: "abc")
}
  

Это позволяет вам сделать параметр по умолчанию не просто «чем-то, что может быть инициализировано с помощью литерала "abc" «, а тем, что вы на самом деле имеете в виду: значением по умолчанию является строка «abc».

Как правило, параметры по умолчанию — это просто удобства для перегрузок, поэтому обычно вы можете заменить любой параметр по умолчанию явной перегрузкой, в которой этот параметр отсутствует.

Комментарии:

1. @LeoDabus Я не понимаю, как это повлияет на ситуацию с параметрами по умолчанию. Как бы вы реализовали синтаксис doSomething() в качестве расширения в RangeReplaceableCollection?

2. Я думаю, что подход с перегрузкой — это то, что я ищу. Возможно, стоит упомянуть, что мой реальный вариант использования немного сложнее и на самом деле не включает строки, но обсуждение здесь, тем не менее, очень содержательное!