Pertanyaan Mengapa stacktraces Clojure begitu lama?


Saya telah menulis beberapa penafsir / compiler mainan di masa lalu, jadi saya mengasosiasikan stacktraces referensi baris dalam file sumber kompilator dengan bug kompiler.

Jika saya menambahkan definisi fungsi buruk berikut ini ke proyek compojure saya:

(defn dodgy-map []
  {:1 :2 :3})

Lein menolak untuk memulai:

$ lein ring server-headless
Exception in thread "main" java.lang.RuntimeException: Map literal must contain an even number of forms, compiling:(one_man_wiki/views.clj:19)
        at clojure.lang.Compiler.load(Compiler.java:6958)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:457)
        at one_man_wiki.handler$eval1564$loading__4784__auto____1565.invoke(handler.clj:1)
        at one_man_wiki.handler$eval1564.invoke(handler.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6501)
        at clojure.lang.Compiler.load(Compiler.java:6952)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at user$eval1.invoke(NO_SOURCE_FILE:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6500)
        at clojure.lang.Compiler.eval(Compiler.java:6477)
        at clojure.core$eval.invoke(core.clj:2797)
        at clojure.main$eval_opt.invoke(main.clj:297)
        at clojure.main$initialize.invoke(main.clj:316)
        at clojure.main$null_opt.invoke(main.clj:349)
        at clojure.main$main.doInvoke(main.clj:427)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:419)
        at clojure.lang.AFn.applyToHelper(AFn.java:163)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Map literal must contain an even number of forms
        at clojure.lang.Util.runtimeException(Util.java:170)
        at clojure.lang.LispReader$MapReader.invoke(LispReader.java:1071)
        at clojure.lang.LispReader.readDelimitedList(LispReader.java:1126)
        at clojure.lang.LispReader$ListReader.invoke(LispReader.java:962)
        at clojure.lang.LispReader.read(LispReader.java:180)
        at clojure.lang.Compiler.load(Compiler.java:6949)
        ... 51 more

Jika saya mereferensikan variabel yang tidak ada:

(defn no-such-variable []
  i-dont-exist)

Saya mendapatkan traceback yang sama dengan ginormous:

Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: i-dont-exist in this context, compiling:(one_man_wiki/views.clj:18)
        at clojure.lang.Compiler.analyze(Compiler.java:6281)
        at clojure.lang.Compiler.analyze(Compiler.java:6223)
        at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5618)
        at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5054)
        at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3674)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6453)
        at clojure.lang.Compiler.analyze(Compiler.java:6262)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6443)
        at clojure.lang.Compiler.analyze(Compiler.java:6262)
        at clojure.lang.Compiler.access$100(Compiler.java:37)
        at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:518)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6455)
        at clojure.lang.Compiler.analyze(Compiler.java:6262)
        at clojure.lang.Compiler.analyze(Compiler.java:6223)
        at clojure.lang.Compiler.eval(Compiler.java:6515)
        at clojure.lang.Compiler.load(Compiler.java:6952)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:457)
        at one_man_wiki.handler$eval1564$loading__4784__auto____1565.invoke(handler.clj:1)
        at one_man_wiki.handler$eval1564.invoke(handler.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6501)
        at clojure.lang.Compiler.load(Compiler.java:6952)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at user$eval1.invoke(NO_SOURCE_FILE:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6500)
        at clojure.lang.Compiler.eval(Compiler.java:6477)
        at clojure.core$eval.invoke(core.clj:2797)
        at clojure.main$eval_opt.invoke(main.clj:297)
        at clojure.main$initialize.invoke(main.clj:316)
        at clojure.main$null_opt.invoke(main.clj:349)
        at clojure.main$main.doInvoke(main.clj:427)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:419)
        at clojure.lang.AFn.applyToHelper(AFn.java:163)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Unable to resolve symbol: i-dont-exist in this context
        at clojure.lang.Util.runtimeException(Util.java:170)
        at clojure.lang.Compiler.resolveIn(Compiler.java:6766)
        at clojure.lang.Compiler.resolve(Compiler.java:6710)
        at clojure.lang.Compiler.analyzeSymbol(Compiler.java:6671)
        at clojure.lang.Compiler.analyze(Compiler.java:6244)
        ... 66 more

Mengapa compiler Clojure tidak menaikkan ClojureSyntaxError dan a ClojureNameError yang bisa ditangkap di tingkat atas dan kesalahan sederhana ditampilkan? Ini adalah kesalahan umum pengembang selama pengembangan.

Jika tracebacks lama berguna dalam beberapa keadaan, mengapa mereka terpotong?

Edit: Apa yang saya cari dalam sebuah jawaban:

  1. Apakah ada situasi (beberapa metaprogramming dengan interop Java mungkin?) Ketika mendapatkan referensi traceback clojure.lang berguna?
  2. (Terkait) Apakah ada kendala teknis yang mencegah penambahan ClojureSyntaxError seperti yang saya jelaskan di atas? Apakah saya pantas membuka masalah pada pelacak bug Clojure? (perbarui: saya sudah membuka CLJ-1155)
  3. Bagaimana pengalaman pemrogram Clojure membaca tracebacks ini? Apakah ada alat untuk membantu? Apakah semua orang menggunakan clj-stacktrace?

32
2018-01-12 19:27


asal


Jawaban:


Membalas poin bernomor Anda,

  1. itu adalah idiomatik di Clojure untuk beroperasi secara langsung dengan pustaka Java, sehingga membuat stacktrace Java penuh dapat membantu jika Anda memanggil objek Java dengan cara yang tidak terduga atau tidak didukung.

  2. Kedengarannya seperti ide yang bagus; Saya sudah sering setidaknya berharap untuk opsi yang dapat diatur yang akan memungkinkan saya untuk melihat hanya bagian-bagian stacktraces yang berasal dari kode saya sendiri, menekan semua lapisan bahasa yang mendasarinya.

  3. Saya biasanya hanya melakukannya dengan cara yang sulit dan meneliti stacktrace untuk mendapatkan garis di program saya yang dihaluskan, menyetel clojure.* bagian (dan saya biasanya menguji setiap menit perubahan jadi saya punya ide bagus apa yang menyebabkan perubahan masalah). Beberapa alat Emacs dan Eclipse yang saya gunakan hanya menunjukkan kesalahan yang sebenarnya dan bukan seluruh stacktrace; Saya biasanya menemukan ini menjadi lebih bermanfaat.

    Di Clojure / barat pada tahun 2012, @chouser memberi pembicaraan yang bagus [PDF] pada anatomi stacktraces, menjelaskan cara membacanya, dan memperkenalkan alat yang tampak menjanjikan, yang tampaknya masih belum melihat cahaya siang hari.

Dibandingkan dengan, katakanlah, Python, yang stacktraces saya temukan sangat user-friendly, saya pikir stacktraces adalah ujung kasar di Clojure, terutama untuk pemula. Hal ini sebagian disebabkan oleh sifat bahasa yang "di-host", meskipun saya berharap ada perbaikan yang dapat dilakukan tanpa menambah kerumitan insidental.


13
2018-01-12 22:24



Stacktraces Clojure berisik, dan itu adalah keluhan yang tampaknya telah terdengar. Beberapa libraires menyediakan stacktraces yang lebih baik, seperti clj-stacktrace atau perpustakaan harapan.

Tapi ini adalah sesuatu yang juga sedang diperbaiki di Clojure sendiri, misalnya dengan rilis 1,7 terbaru. Lihat juga api clojure.stacktrace.

Jadi saya yakin itu hanya akan menjadi lebih baik dari waktu ke waktu. Hal-hal sangat menarik dalam ekosistem Clojure {Script} sekarang.


1
2017-07-10 08:09