Swift Compiler-Fehler: "Ausdruck zu komplex" bei einer Zeichenfolgenverkettung

143

Ich finde das mehr als alles andere amüsant. Ich habe es behoben, aber ich wundere mich über die Ursache. Hier ist der Fehler : DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Warum beschwert es sich? Es scheint einer der einfachsten Ausdrücke zu sein, die möglich sind.

Der Compiler zeigt auf den columns + ");";Abschnitt

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);
}

Das Update ist:

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

das funktioniert auch (via @efischency) aber ich mag es nicht so sehr, weil ich denke das geht (verloren:

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

Kendrick Taylor
quelle
10
Hast du gesehen, ob das funktioniert : var statement = "create table if not exists \(self.tableName()) (\(columns))"?
Efischency
5
Die von @efischency empfohlene String-Interpolation ist im Allgemeinen eine bessere Option als die manuelle Verkettung mit +.
Mattt
5
Sicher, aber darum geht es nicht. Es ist mir egal, ob es der "vorgeschlagene" Weg ist oder nicht, ich möchte nur wissen, warum der Compiler daran erstickt. Ich habe eine Lösung, die funktioniert. Es geht nicht darum, den Fehler zu beheben, sondern den Fehler zu verstehen.
Kendrick Taylor
2
Nach allem, was ich gehört habe, ist der Swift-Compiler noch in Arbeit. Das Team könnte sich über einen Fehlerbericht freuen.
Molbdnilo
Ich hatte kein Problem damit, dies mit 6.3.1 zu kompilieren. Ich hatte in der Vergangenheit ähnliche lächerliche Nachrichten. Wir müssen warten, bis Swift seinen Alpha-Zustand verlässt.
QWERTY_SO

Antworten:

183

Ich bin kein Experte für Compiler - ich weiß nicht, ob diese Antwort "Ihre Denkweise auf sinnvolle Weise ändern wird", aber ich verstehe das Problem folgendermaßen:

Es hat mit Typinferenz zu tun. Jedes Mal, wenn Sie den +Operator verwenden, muss Swift alle möglichen Überladungen durchsuchen +und daraus schließen, welche Version von +Ihnen verwendet wird. Ich habe knapp 30 Überladungen für den +Bediener gezählt. Das sind viele Möglichkeiten, und wenn Sie 4 oder 5 +Operationen miteinander verketten und den Compiler bitten, alle Argumente abzuleiten, fragen Sie viel mehr, als es auf den ersten Blick erscheinen mag.

Diese Schlussfolgerung kann kompliziert werden. Wenn Sie beispielsweise ein UInt8und ein Intusing hinzufügen +, ist die Ausgabe ein Int, aber es gibt einige Arbeiten, die in die Bewertung der Regeln für das Mischen von Typen mit Operatoren einfließen.

Und wenn Sie Literale verwenden, wie die StringLiterale in Ihrem Beispiel, erledigt der Compiler die Arbeit, das StringLiteral in a zu konvertieren Stringund dann das Argument und die Rückgabetypen für den +Operator usw. abzuleiten .

Wenn ein Ausdruck ausreichend komplex ist, dh der Compiler muss zu viele Rückschlüsse auf die Argumente und Operatoren ziehen, wird er beendet und weist Sie darauf hin, dass er beendet wird.

Es ist beabsichtigt, den Compiler zu beenden, sobald ein Ausdruck eine bestimmte Komplexität erreicht. Die Alternative besteht darin, den Compiler versuchen zu lassen, dies zu tun, und zu prüfen, ob dies möglich ist. Dies ist jedoch riskant. Der Compiler könnte es für immer versuchen, festsitzen oder einfach abstürzen. Mein Verständnis ist also, dass es einen statischen Schwellenwert für die Komplexität eines Ausdrucks gibt, über den der Compiler nicht hinausgeht.

Ich verstehe, dass das Swift-Team an Compiler-Optimierungen arbeitet, die diese Fehler seltener machen. Klicken Sie auf diesen Link, um in den Apple Developer-Foren ein wenig darüber zu erfahren .

In den Dev-Foren hat Chris Lattner die Leute gebeten, diese Fehler als Radarberichte einzureichen, weil sie aktiv daran arbeiten, sie zu beheben.

So verstehe ich es, nachdem ich einige Beiträge hier und im Dev-Forum darüber gelesen habe, aber mein Verständnis von Compilern ist naiv, und ich hoffe, dass jemand mit einem tieferen Wissen darüber, wie er mit diesen Aufgaben umgeht, meine Arbeit erweitert habe hier geschrieben.

Aaron Rasmussen
quelle
Ich habe mir etwas in diesem Sinne ausgedacht, aber es war trotzdem eine hilfreiche Antwort. Danke für die Antwort. Haben Sie die Anzahl der + Operatoren von Hand gezählt oder gibt es einen raffinierten Weg, den ich nicht kenne?
Kendrick Taylor
Ich habe es mir auf SwiftDoc.org angesehen und von Hand gezählt. Dies ist die Seite, über die ich spreche: swiftdoc.org/operator/pls
Aaron Rasmussen
28
Dies ist ein Fehler, unabhängig davon, ob sie es so nennen. Die Compiler anderer Sprachen haben kein Problem mit Code, der dem veröffentlichten Code ähnelt. Es ist dumm, dem Endbenutzer vorzuschlagen, das Problem zu beheben.
John
7
Typinferenz? Was bringt es in dieser lächerlichen Situation, eine stark typisierte Sprache wie Swift zu haben (in der Sie String + Int nicht einmal verketten können, ohne das Int wirken zu müssen)? Wieder einmal versucht Swift, Probleme zu lösen, die überhaupt niemand hatte.
Azurlake
10
@ John Kein Fehler, nur schlechtes Sprachdesign, wenn du mich fragst! Swift geht zu weit, nur um anders zu sein.
T. Rex
31

Dies ist fast die gleiche wie die akzeptierte Antwort, aber mit einigen zusätzlichen Dialogen (ich hatte mit Rob Napier, seinen anderen Antworten und Matt, Oliver, David von Slack) und Links.

Siehe die Kommentare in dieser Diskussion. Der Kern davon ist:

+ ist stark überlastet (Apple scheint dies in einigen Fällen behoben zu haben)

Der +Operator ist stark überlastet, da er derzeit 27 verschiedene Funktionen hat. Wenn Sie also 4 Zeichenfolgen verketten, dh 3 +Operatoren haben, muss der Compiler jedes Mal zwischen 27 Operatoren prüfen , das sind also 27 ^ 3-mal. Aber das ist es nicht.

Es wird auch überprüft , ob das lhsund rhsdas der +Funktionen beide gültig sind, wenn sie appendaufgerufen werden, um das aufgerufene zu kernen . Dort können Sie sehen, dass eine Reihe von etwas intensiven Überprüfungen auftreten können. Wenn die Zeichenfolge nicht zusammenhängend gespeichert ist, scheint dies der Fall zu sein, wenn die Zeichenfolge, mit der Sie sich befassen, tatsächlich mit NSString verbunden ist. Swift muss dann alle Byte-Array-Puffer zu einem einzigen zusammenhängenden Puffer zusammensetzen, was das Erstellen neuer Puffer auf dem Weg erfordert. und dann erhalten Sie schließlich einen Puffer, der die Zeichenfolge enthält, die Sie miteinander verketten möchten.

Auf den Punkt gebracht ist es 3 Cluster von Compiler überprüft , die Sie nach unten , dh verlangsamt jeder Unterausdruck hat angesichts der alles überdacht werden es könnte zurückkehren . Als Ergebnis Strings mit Interpolation verketten dh unter Verwendung " My fullName is \(firstName) \(LastName)"ist viel besser als "My firstName is" + firstName + LastNameda Interpolation nicht hat jede Überlastung hat

Swift 3 hat einige Verbesserungen vorgenommen. Weitere Informationen finden Sie unter Zusammenführen mehrerer Arrays, ohne den Compiler zu verlangsamen. . Trotzdem ist der +Operator immer noch überlastet und es ist besser, die String-Interpolation für längere Strings zu verwenden


Verwendung von Optionen (laufendes Problem - Lösung verfügbar)

In diesem sehr einfachen Projekt:

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"
}

Die Kompilierungszeit für die Funktionen ist wie folgt:

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()

Beachten Sie, wie verrückt die Kompilierungsdauer concatenatedOptionalsist.

Dies kann gelöst werden durch:

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

welches kompiliert in 88ms

Die Ursache des Problems ist , dass der Compiler identifiziert nicht die ""als ein String. Es ist eigentlichExpressibleByStringLiteral

Der Compiler sieht alle Typen, die diesem Protokoll entsprechen , ??und muss sie durchlaufen , bis er einen Typ findet, der standardmäßig verwendet werden kann String. Durch die Verwendung von emptyStringHardcodierung Stringmuss der Compiler nicht mehr alle konformen Typen von durchlaufenExpressibleByStringLiteral

Informationen zum Protokollieren der Kompilierungszeiten finden Sie hier oder hier


Andere ähnliche Antworten von Rob Napier auf SO:

Warum dauert das Erstellen von Strings so lange?

Wie kann man mehrere Arrays zusammenführen, ohne den Compiler zu verlangsamen?

Swift Array enthält Funktionen, die die Build-Zeiten verlängern

Honig
quelle
19

Das ist ziemlich lächerlich, egal was du sagst! :) :)

Geben Sie hier die Bildbeschreibung ein

Aber das geht leicht vorbei

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
Karim
quelle
2

Ich hatte ein ähnliches Problem:

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

In Xcode 9.3 sieht die Zeile folgendermaßen aus:

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

Nachdem Sie es in so etwas geändert haben:

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

es hat alles geklappt.

Wahrscheinlich hat es etwas damit zu tun, dass der Swift-Compiler versucht, den Datentyp aus dem Code abzuleiten.

Vedrano
quelle