Ошибка компилятора Swift: «Слишком сложное выражение» при конкатенации строк

143

Я нахожу это забавным больше всего на свете. Я исправил это, но я задаюсь вопросом о причине. Здесь ошибка: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Почему это жалуется? Кажется, это одно из самых простых возможных выражений.

Компилятор указывает на columns + ");";раздел

func tableName() -> String { return("users"); } 

func createTableStatement(schema: [String]) -> String {

    var schema = schema;

    schema.append("id string");
    schema.append("created integer");
    schema.append("updated integer");
    schema.append("model blob");

    var columns: String = ",".join(schema);

    var statement = "create table if not exists " + self.tableName() + "(" + columns + ");";

    return(statement);
}

исправление:

var statement = "create table if not exists " + self.tableName();
statement += "(" + columns + ");";

это также работает (через @efischency), но мне это не очень нравится, потому что я думаю, что (заблудиться:

var statement = "create table if not exists \(self.tableName()) (\(columns))"

Кендрик Тейлор
источник
10
Видели ли вы , если это работает: var statement = "create table if not exists \(self.tableName()) (\(columns))"?
efischency
5
Строковая интерполяция, как рекомендует @efischency, как правило, является лучшим вариантом, чем ручная конкатенация +.
Мэтт
5
Конечно, но это не главное. Мне все равно, если это «предложенный» способ или нет, я просто хочу знать, почему компилятор его подавляет. У меня есть решение, которое работает, дело не в исправлении ошибки, а в понимании ошибки.
Кендрик Тейлор
2
Из того, что я слышал, компилятор Swift все еще находится в стадии разработки. Команда может оценить сообщение об ошибке по этому вопросу.
molbdnilo
У меня не было проблем, компилируя это с 6.3.1. У меня были подобные нелепые сообщения в прошлом. Нам нужно подождать, пока Свифт не выйдет из своего альфа-состояния.
qwerty_so

Ответы:

183

Я не эксперт по компиляторам - я не знаю, изменит ли этот ответ «значимое изменение вашего мышления», но мое понимание проблемы таково:

Это связано с выводом типа. Каждый раз, когда вы используете +оператор, Swift должен искать все возможные перегрузки +и определять, какую версию +вы используете. Я насчитал чуть менее 30 перегрузок для +оператора. Это много возможностей, и когда вы +объединяете 4 или 5 операций вместе и просите компилятор вывести все аргументы, вы запрашиваете гораздо больше, чем может показаться на первый взгляд.

Этот вывод может усложниться - например, если вы добавите a UInt8и Intиспользование +, выходной результат будет a Int, но есть некоторая работа по оценке правил смешивания типов с операторами.

И когда вы используете литералы, такие как Stringлитералы в вашем примере, компилятор выполняет преобразование Stringлитерала в a String, а затем выводит аргументы и возвращаемые типы для +оператора и т. Д.

Если выражение достаточно сложное, т. Е. Требует, чтобы компилятор сделал слишком много выводов об аргументах и ​​операторах, оно завершает работу и сообщает, что оно завершается.

Завершение работы компилятора, когда выражение достигает определенного уровня сложности, является намеренным. Альтернатива состоит в том, чтобы позволить компилятору попробовать и сделать это, и посмотреть, может ли это, но это рискованно - компилятор может продолжать пытаться вечно, зависать или просто падать. Поэтому я понимаю, что для сложности выражения существует статический порог, который компилятор не выйдет за пределы.

Насколько я понимаю, команда Swift работает над оптимизацией компилятора, которая сделает эти ошибки менее распространенными. Вы можете немного узнать об этом на форумах Apple Developer, нажав на эту ссылку .

На форумах Dev Крис Латтнер просил людей регистрировать эти ошибки как отчеты радаров, потому что они активно работают над их исправлением.

Вот как я понимаю это после прочтения ряда постов здесь и на форуме разработчиков об этом, но мое понимание компиляторов наивно, и я надеюсь, что кто-то с более глубоким знанием того, как они справляются с этими задачами, расширит то, что я написал здесь.

Аарон Расмуссен
источник
Я понял кое-что на этот счет, но это был полезный ответ, тем не менее. Спасибо за ответы. Вы подсчитали количество операторов + вручную или есть какой-то хитрый способ, о котором я не знаю?
Кендрик Тейлор
Я просто посмотрел на SwiftDoc.org и посчитал их от руки. Это страница, о которой я говорю: swiftdoc.org/operator/pls
Аарон Расмуссен
28
Это ошибка, независимо от того, назовут ли они это так. Компиляторы других языков не имеют проблем с кодом, аналогичным тому, который был опубликован. Предлагать конечному пользователю исправить это глупо.
Джон
7
Тип логического вывода? Какой смысл иметь язык со строгой типизацией, такой как Swift (в котором вы даже не можете объединить String + Int без использования Int) в этой нелепой ситуации? Еще раз, Swift пытается решить проблемы, которые никто не имел в первую очередь.
Азурлейк
10
@ Джон Не ошибка, просто плохой языковой дизайн, если вы спросите меня! Свифт заходит слишком далеко, просто пытаясь быть другим.
Т. Рекс
31

Это почти то же самое, что принятый ответ, но с некоторым дополнительным диалогом (я имел с Робом Нейпиром, его другими ответами и Мэттом, Оливером, Дэвидом из Slack) и ссылками.

Смотрите комментарии в этой дискуссии. Суть этого:

+ сильно перегружен (в некоторых случаях Apple исправила это)

+Оператор сильно перегружен, как сейчас она имеет 27 различных функций , так что если вы конкатенация 4 строки , то есть у вас есть 3 +операторов, компилятор должен проверить между 27 операторами , каждый раз, так что 27 ^ 3 раза. Но это не так.

Существует также проверка , чтобы увидеть , если lhsи rhsиз +функций являются действительными , если они вызываются через к сердечнику appendназывается. Там вы можете увидеть несколько интенсивных проверок, которые могут произойти. Если строка хранится несмежно, что имеет место, если строка, с которой вы имеете дело, фактически соединена с NSString. Затем Swift должен собрать все буферы байтового массива в единый непрерывный буфер, который требует создания новых буферов. и затем вы в конечном итоге получите один буфер, содержащий строку, которую вы пытаетесь объединить вместе.

В двух словах, есть 3 кластера проверок компилятора, которые замедляют работу, т. Е. Каждое подвыражение должно быть пересмотрено в свете всего, что оно может вернуть . В результате объединение строк с интерполяцией, т. Е. Использование " My fullName is \(firstName) \(LastName)"намного лучше, чем, "My firstName is" + firstName + LastNameпоскольку интерполяция не имеет перегрузки

Swift 3 внесла некоторые улучшения. Для получения дополнительной информации прочитайте Как объединить несколько массивов без замедления компилятора? , Тем не менее, +оператор по-прежнему перегружен, и для более длинных строк лучше использовать интерполяцию строк.


Использование опций (текущая проблема - решение доступно)

В этом очень простом проекте:

import UIKit

class ViewController: UIViewController {

    let p = Person()
    let p2 = Person2()

    func concatenatedOptionals() -> String {
        return (p2.firstName ?? "") + "" + (p2.lastName ?? "") + (p2.status ?? "")
    }

    func interpolationOptionals() -> String {
        return "\(p2.firstName ?? "") \(p2.lastName ?? "")\(p2.status ?? "")"
    }

    func concatenatedNonOptionals() -> String {
        return (p.firstName) + "" + (p.lastName) + (p.status)
    }

    func interpolatedNonOptionals() -> String {
        return "\(p.firstName) \(p.lastName)\(p.status)"
    }
}


struct Person {
    var firstName = "Swift"
    var lastName = "Honey"
    var status = "Married"
}

struct Person2 {
    var firstName: String? = "Swift"
    var lastName: String? = "Honey"
    var status: String? = "Married"
}

Время компиляции для функций таково:

21664.28ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:16:10 instance method concatenatedOptionals()
2.31ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:20:10 instance method interpolationOptionals()
0.96ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:24:10 instance method concatenatedNonOptionals()
0.82ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:28:10 instance method interpolatedNonOptionals()

Обратите внимание, насколько сумасшедшей является высокая продолжительность компиляции concatenatedOptionals.

Это можно решить, выполнив:

let emptyString: String = ""
func concatenatedOptionals() -> String {
    return (p2.firstName ?? emptyString) + emptyString + (p2.lastName ?? emptyString) + (p2.status ?? emptyString)
}

который компилируется в 88ms

Основной причиной проблемы является то, что компилятор не идентифицирует ""как String. Это на самом делеExpressibleByStringLiteral

Компилятор увидит ??и должен будет перебрать все типы, которые соответствуют этому протоколу , пока не найдет тип, который может быть значением по умолчанию String. Используя emptyStringжестко запрограммированный код String, компилятору больше не нужно перебирать все соответствующие типыExpressibleByStringLiteral

Чтобы узнать, как регистрировать время компиляции, смотрите здесь или здесь


Другие подобные ответы Роба Нейпира на SO:

Почему сложение строк занимает так много времени?

Как объединить несколько массивов, не замедляя компилятор?

Swift Array содержит функцию, увеличивающую время сборки

Мед
источник
19

Это довольно нелепо, независимо от того, что вы говорите! :)

введите описание изображения здесь

Но это легко проходит

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
Karim
источник
2

У меня была похожая проблема:

expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions

В Xcode 9.3 строка выглядит так:

let media = entities.filter { (entity) -> Bool in

После изменения в что-то вроде этого:

let media = entities.filter { (entity: Entity) -> Bool in

все получилось.

Возможно, это как-то связано с компилятором Swift, который пытается вывести тип данных из кода.

vedrano
источник