Swift Compiler Error: “Ekspresi terlalu kompleks” pada rangkaian string

143

Saya menemukan ini lucu lebih dari apa pun. Saya sudah memperbaikinya, tapi saya bertanya-tanya tentang penyebabnya. Berikut adalah kesalahan: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Mengapa itu mengeluh? Sepertinya salah satu ungkapan paling sederhana yang mungkin.

Kompilator menunjuk ke columns + ");";bagian

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

Cara mengatasinya adalah:

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

ini juga berfungsi (via @fischency) tetapi saya tidak terlalu menyukainya karena saya pikir yang (hilang:

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

Kendrick Taylor
sumber
10
Apakah Anda melihat apakah ini berhasil var statement = "create table if not exists \(self.tableName()) (\(columns))":?
efischency
5
Interpolasi string, seperti yang direkomendasikan oleh @efischency, umumnya merupakan opsi yang lebih baik daripada penggabungan manual +.
mattt
5
Tentu, tapi bukan itu intinya. Saya tidak peduli apakah itu cara yang "disarankan" atau tidak, saya hanya ingin tahu mengapa kompiler tersedak karenanya. Saya punya solusi yang berfungsi, ini bukan tentang memperbaiki kesalahan, ini tentang memahami kesalahan.
Kendrick Taylor
2
Dari apa yang saya dengar, kompiler Swift masih sangat banyak pekerjaan yang sedang berjalan. Tim mungkin menghargai laporan bug tentang ini.
molbdnilo
Saya tidak punya masalah mengkompilasi ini dengan 6.3.1. Saya memiliki pesan konyol serupa di masa lalu. Kita harus menunggu sampai Swift meninggalkan status alfa.
qwerty_so

Jawaban:

183

Saya bukan ahli kompiler - saya tidak tahu apakah jawaban ini akan "mengubah cara Anda berpikir dengan cara yang bermakna," tetapi pemahaman saya tentang masalahnya adalah ini:

Ini ada hubungannya dengan inferensi tipe. Setiap kali Anda menggunakan +operator, Swift harus mencari semua kemungkinan kelebihan +dan menyimpulkan versi yang +Anda gunakan. Saya menghitung di bawah 30 kelebihan untuk +operator. Itu banyak kemungkinan, dan ketika Anda rantai 4 atau 5 +operasi bersama dan meminta kompiler untuk menyimpulkan semua argumen, Anda meminta lebih banyak daripada yang mungkin muncul pada pandangan pertama.

Kesimpulan itu bisa menjadi rumit - misalnya, jika Anda menambahkan UInt8dan Intmenggunakan +, hasilnya akan menjadi Int, tetapi ada beberapa pekerjaan yang digunakan untuk mengevaluasi aturan untuk mencampur jenis dengan operator.

Dan ketika Anda menggunakan literal, seperti Stringliteral dalam contoh Anda, kompiler melakukan pekerjaan mengubah Stringliteral menjadi a String, dan kemudian melakukan pekerjaan menyimpulkan argumen dan mengembalikan tipe untuk +operator, dll.

Jika sebuah ekspresi cukup kompleks - yaitu, ia membutuhkan kompiler untuk membuat terlalu banyak kesimpulan tentang argumen dan operator - itu berhenti dan memberitahu Anda bahwa itu berhenti.

Membuat kompiler berhenti setelah ekspresi mencapai tingkat kompleksitas tertentu disengaja. Alternatifnya adalah membiarkan kompiler mencoba dan melakukannya, dan melihat apakah itu bisa, tetapi itu berisiko - kompiler dapat terus mencoba selamanya, macet, atau hanya crash. Jadi pemahaman saya adalah bahwa ada ambang statis untuk kompleksitas ekspresi yang tidak akan dikompilasi oleh kompiler.

Pemahaman saya adalah bahwa tim Swift sedang mengerjakan optimisasi kompiler yang akan membuat kesalahan ini lebih jarang terjadi. Anda dapat belajar sedikit tentang hal itu di forum Apple Developer dengan mengklik link ini .

Di forum Dev, Chris Lattner telah meminta orang untuk melaporkan kesalahan ini sebagai laporan radar, karena mereka secara aktif bekerja untuk memperbaikinya.

Itulah bagaimana saya memahaminya setelah membaca sejumlah posting di sini dan di forum Dev tentang hal itu, tetapi pemahaman saya tentang kompiler adalah naif, dan saya berharap bahwa seseorang dengan pengetahuan yang lebih dalam tentang bagaimana mereka menangani tugas-tugas ini akan memperluas apa yang saya telah menulis di sini.

Aaron Rasmussen
sumber
Saya menemukan sesuatu untuk efek itu, tetapi itu adalah jawaban yang sangat membantu. Terimakasih telah menjawab. Apakah Anda menghitung jumlah operator + dengan tangan atau apakah ada cara yang tidak saya sadari?
Kendrick Taylor
Saya hanya mengintipnya di SwiftDoc.org dan menghitungnya dengan tangan. Ini adalah halaman yang saya bicarakan: swiftdoc.org/operator/pls
Aaron Rasmussen
28
Ini adalah bug, terlepas dari apakah mereka akan menyebutnya demikian. Kompiler bahasa lain tidak memiliki masalah dengan kode yang mirip dengan apa yang diposting. Menyarankan pengguna akhir untuk memperbaikinya itu konyol.
Yohanes
7
Ketikkan inferensi? Apa gunanya memiliki bahasa yang diketik kuat seperti Swift (di mana Anda bahkan tidak bisa menggabungkan String + Int tanpa harus membuang Int) dalam situasi yang penuh teka-teki ini? Sekali lagi, Swift mencoba memecahkan masalah yang tidak dimiliki siapa pun sejak awal.
Azurlake
10
@ John Bukan bug, hanya desain bahasa yang buruk jika Anda bertanya kepada saya! Swift melangkah terlalu jauh hanya dengan mencoba tampil beda.
T. Rex
31

Ini hampir sama dengan jawaban yang diterima tetapi dengan beberapa dialog tambahan (saya miliki dengan Rob Napier, jawaban lainnya dan Matt, Oliver, David dari Slack) dan tautan.

Lihat komentar dalam diskusi ini . Intinya adalah:

+ sangat kelebihan beban (Apple tampaknya telah memperbaiki ini untuk beberapa kasus)

The +Operator ini sangat kelebihan beban, seperti yang sekarang memiliki 27 fungsi yang berbeda jadi jika Anda concatenating 4 senar yaitu Anda memiliki 3 +operator compiler harus memeriksa antara 27 operator setiap kali, jadi itu 27 ^ 3 kali. Tapi bukan itu.

Ada juga cek untuk melihat apakah lhsdan rhsdari +fungsi keduanya berlaku jika mereka itu panggilan melalui ke inti appenddisebut. Di sana Anda dapat melihat ada sejumlah pemeriksaan agak intensif yang dapat terjadi. Jika string disimpan secara tidak bersamaan, yang tampaknya menjadi kasus jika string yang Anda hadapi sebenarnya dijembatani ke NSString. Swift kemudian harus merakit kembali semua buffer array byte menjadi buffer yang berdekatan dan yang membutuhkan membuat buffer baru di sepanjang jalan. dan akhirnya Anda mendapatkan satu buffer yang berisi string yang Anda coba gabungkan bersama.

Singkatnya ada 3 kelompok pemeriksaan kompiler yang akan memperlambat Anda yaitu masing-masing sub-ekspresi harus dipertimbangkan kembali mengingat segala hal yang mungkin kembali . Alhasil string yang disatukan dengan interpolasi yaitu menggunakan " My fullName is \(firstName) \(LastName)"jauh lebih baik daripada "My firstName is" + firstName + LastNamekarena interpolasi tidak memiliki kelebihan

Swift 3 telah membuat beberapa peningkatan. Untuk informasi lebih lanjut, baca Bagaimana cara menggabungkan banyak Array tanpa memperlambat kompiler? . Meskipun demikian +operator masih kelebihan beban dan lebih baik menggunakan interpolasi string untuk string yang lebih lama


Penggunaan opsional (masalah berkelanjutan - solusi tersedia)

Dalam proyek yang sangat sederhana ini:

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

Waktu kompilasi untuk fungsi adalah sebagai berikut:

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

Perhatikan betapa gila durasi kompilasi concatenatedOptionalsini.

Ini dapat diselesaikan dengan melakukan:

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

yang mengkompilasi di 88ms

Akar penyebab masalah adalah bahwa kompiler tidak mengidentifikasi ""sebagai a String. SebenarnyaExpressibleByStringLiteral

Compiler akan melihat ??dan harus mengulang semua tipe yang sesuai dengan protokol ini , sampai ia menemukan tipe yang dapat menjadi default String. Dengan Menggunakan emptyStringhardcoded String, kompiler tidak perlu lagi mengulang semua tipe yang sesuaiExpressibleByStringLiteral

Untuk mempelajari cara mencatat waktu kompilasi, lihat di sini atau di sini


Jawaban serupa lainnya oleh Rob Napier di SO:

Mengapa penambahan string membutuhkan waktu lama untuk dibangun?

Bagaimana cara menggabungkan banyak Array tanpa memperlambat kompiler?

Swift Array berisi fungsi membuat waktu build menjadi panjang

Madu
sumber
19

Ini sangat konyol tidak peduli apa yang Anda katakan! :)

masukkan deskripsi gambar di sini

Tapi ini bisa dilewati dengan mudah

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
karim
sumber
2

Saya memiliki masalah serupa:

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

Dalam Xcode 9.3 baris berbunyi seperti ini:

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

Setelah mengubahnya menjadi seperti ini:

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

semuanya berhasil.

Mungkin ada hubungannya dengan kompiler Swift yang mencoba menyimpulkan tipe data dari kode sekitar.

vedrano
sumber