Bisakah suatu fungsi atau makro menentukan peringatan byte-compiler?

15

Saya sedang menulis fungsi yang, pada prinsipnya, mengambil sejumlah argumen. Dalam prakteknya, bagaimanapun, harus hanya pernah melewati bahkan jumlah argumen, dan akan menghasilkan hasil yang tidak diinginkan sebaliknya.

Berikut ini contoh contoh untuk konteks:

(defun my-caller (&rest args)
  (while args
    (call-other-function (pop args) (pop args))))

Ketika file elisp dikompilasi dengan byte, byte-compiler memberikan peringatan ketika melihat fungsi dipanggil dengan jumlah argumen yang salah. Jelas, itu tidak akan pernah terjadi my-caller, karena itu ditetapkan untuk mengambil nomor berapa pun.

Namun, mungkin ada properti simbol yang bisa saya atur, atau (declare)bentuk yang bisa saya tambahkan ke definisinya. Sesuatu untuk memberi tahu pengguna bahwa fungsi ini seharusnya hanya diberikan sejumlah argumen.

  1. Apakah ada cara untuk menginformasikan byte-compiler tentang pembatasan ini?
  2. Jika tidak, Apakah mungkin dengan makro, alih-alih fungsi?
Malabarba
sumber
"... ketika melihat fungsi yang dipanggil dengan salah jumlah argumen"?
itsjeyd

Jawaban:

13

EDIT : Cara yang lebih baik untuk melakukan ini di Emacs baru-baru ini adalah dengan mendefinisikan makro kompiler untuk memeriksa jumlah argumen. Jawaban asli saya menggunakan makro normal dipertahankan di bawah ini, tetapi makro kompiler lebih unggul karena tidak mencegah melewatkan fungsi ke funcallatau applysaat runtime.

Di versi terbaru Emacs, Anda bisa melakukan ini dengan mendefinisikan kompiler-makro untuk fungsi Anda yang memeriksa jumlah argumen dan menghasilkan peringatan (atau bahkan kesalahan) jika tidak cocok. Satu-satunya kehalusan adalah bahwa makro kompiler harus mengembalikan bentuk panggilan fungsi asli tidak berubah untuk evaluasi atau kompilasi. Ini dilakukan dengan menggunakan &wholeargumen dan mengembalikan nilainya. Ini bisa dicapai seperti ini:

(require 'cl-lib)

(defun my-caller (&rest args)
  (while args
    (message "%S %S" (pop args) (pop args))))

(define-compiler-macro my-caller (&whole form &rest args)
  (when (not (cl-evenp (length args)))
    (byte-compile-warn "`my-caller' requires an even number of arguments"))
  form)

(my-caller 1 2 3 4)
(my-caller 1 2)
(funcall #'my-caller 1 2 3 4)       ; ok
(apply #'my-caller '(1 2))          ; also ok
(my-caller 1)                       ; produces a warning
(funcall #'my-caller 1 2 3)         ; no warning!
(apply #'my-caller '(1 2 3))        ; also no warning

Catat itu funcalldan applysekarang bisa digunakan, tetapi mereka mem-bypass pengecekan oleh kompiler makro. Meskipun nama mereka, macro compiler juga tampaknya akan diperluas dalam perjalanan 'ditafsirkan' evaluasi melalui C-xC-e, M-xeval-buffer, sehingga Anda akan mendapatkan kesalahan pada evaluasi serta pada kompilasi contoh ini.


Jawaban asli berikut:

Inilah cara Anda menerapkan saran Jordon untuk "menggunakan makro yang akan memberikan peringatan pada waktu ekspansi". Ternyata sangat mudah:

(require 'cl-lib)

(defmacro my-caller (&rest args)
  (if (cl-evenp (length args))
      `(my-caller--function ,@args)
    (error "Function `my-caller' requires an even number of arguments")))

(defun my-caller--function (&rest args)
  ;; function body goes here
  args)

(my-caller 1 2 3 4)
(my-caller 1 2 3)

Mencoba mengkompilasi hal di atas dalam file akan gagal (tidak ada .elcfile yang dihasilkan), dengan pesan kesalahan yang dapat diklik bagus di kompilasi log, menyatakan:

test.el:14:1:Error: `my-caller' requires an even number of arguments

Anda juga dapat mengganti (error …)dengan (byte-compile-warn …)untuk menghasilkan peringatan alih-alih kesalahan, memungkinkan kompilasi untuk melanjutkan. (Terima kasih kepada Jordon karena menunjukkan ini dalam komentar).

Karena makro diperluas pada waktu kompilasi, tidak ada penalti waktu berjalan terkait dengan pemeriksaan ini. Tentu saja, Anda tidak dapat menghentikan panggilan orang lain my-caller--functionsecara langsung, tetapi setidaknya Anda dapat mengiklankannya sebagai fungsi "pribadi" menggunakan konvensi hyphen-double.

Kerugian penting menggunakan makro untuk tujuan ini adalah bahwa my-callertidak lagi fungsi kelas satu: Anda tidak bisa meneruskannya ke funcallatau applysaat runtime (atau setidaknya itu tidak akan melakukan apa yang Anda harapkan). Dalam hal itu, solusi ini tidak sebaik bisa dengan mudah mendeklarasikan peringatan kompiler untuk fungsi yang sebenarnya. Tentu saja, menggunakan tidak applyakan memungkinkan untuk memeriksa jumlah argumen yang diteruskan ke fungsi pada waktu kompilasi, jadi mungkin ini merupakan trade-off yang dapat diterima.

Jon O.
sumber
2
Peringatan kompilasi dibuat denganbyte-compile-warn
Jordon Biondo
Saya sekarang bertanya-tanya apakah ini bisa lebih efektif dicapai dengan mendefinisikan makro kompiler untuk fungsi tersebut. Ini akan menghilangkan kelemahan dari tidak menjadi applyatau funcallpembungkus makro. Saya akan mencobanya dan mengedit jawaban saya jika berhasil.
Jon O.
11

Ya, Anda dapat menggunakan byte-defop-compileruntuk benar-benar menentukan fungsi yang mengkompilasi fungsi Anda, byte-defop-compilermemiliki beberapa fitur bawaan untuk membantu Anda menentukan bahwa fungsi Anda harus menghasilkan peringatan berdasarkan memiliki sejumlah argumen.

Dokumentasi

Tambahkan bentuk-kompiler untuk FUNCTION. Jika fungsinya adalah simbol, maka variabel "byte-SYMBOL" harus memberi nama opcode yang akan digunakan. Jika fungsi adalah daftar, elemen pertama adalah fungsi dan elemen kedua adalah simbol bytecode. Elemen kedua mungkin nihil, artinya tidak ada opcode. COMPILE-HANDLER adalah fungsi yang digunakan untuk mengkompilasi byte-op ini, atau mungkin singkatan 0, 1, 2, 3, 0-1, atau 1-2. Jika nil, maka handlernya adalah "byte-compile-SYMBOL.


Pemakaian

Dalam kasus spesifik Anda, Anda dapat menggunakan salah satu singkatan untuk menentukan bahwa fungsi Anda harus diberikan dua argumen.

(byte-defop-compiler my-caller 2)

Sekarang fungsi Anda akan memberikan peringatan ketika dikompilasi dengan apa pun kecuali 2 argumen.

Jika Anda ingin memberikan peringatan yang lebih spesifik dan menulis fungsi kompiler Anda sendiri. Lihat byte-compile-one-argdan fungsi serupa lainnya di bytecomp.el untuk referensi.

Perhatikan bahwa Anda tidak hanya menentukan beberapa fungsi untuk menangani validasi, tetapi sebenarnya kompilasi juga. Sekali lagi kompilasi fungsi di bytecomp.el akan memberi Anda referensi yang bagus.


Rute yang Lebih Aman

Ini bukan sesuatu yang saya lihat didokumentasikan atau didiskusikan secara online tetapi secara keseluruhan saya akan mengatakan ini adalah rute yang salah untuk diambil. Rute yang benar (IMO) adalah untuk menulis pembelokan Anda dengan tanda tangan deskriptif atau menggunakan makro yang akan memberikan peringatan pada waktu ekspansi, memeriksa panjang argumen Anda dan menggunakan byte-compile-warnatau erroruntuk menunjukkan kesalahan. Mungkin juga bermanfaat bagi Anda eval-when-compileuntuk melakukan pengecekan kesalahan.

Anda juga perlu mendefinisikan fungsi Anda sebelum digunakan, dan panggilan ke byte-defop-compilerharus sebelum kompilator mendapatkan panggilan sebenarnya dari fungsi Anda.

Sekali lagi, sepertinya tidak benar-benar didokumentasikan atau dinasihati dari apa yang saya lihat (bisa saja salah) tapi saya membayangkan pola untuk mengikuti di sini adalah untuk menentukan beberapa jenis file header untuk paket Anda yang penuh dengan sekelompok pembelotan kosong dan panggilan ke byte-defop-compiler. Ini pada dasarnya akan menjadi paket yang diperlukan sebelum paket asli Anda dapat dikompilasi.

Opini: Berdasarkan pada apa yang saya ketahui, yang tidak banyak, karena saya baru belajar tentang semua ini, saya akan menyarankan Anda untuk tidak melakukan semua ini. pernah

Jordon Biondo
sumber
1
Terkait: Ada bytecomp-simplify yang mengajarkan byte-compiler peringatan tambahan.
Wilfred Hughes