Biên dịch byte của gói đa tập tin: Hàm không được xác định là xác định


7

Hãy tưởng tượng rằng tôi có các tệp sau trong gói (lố bịch) của mình:

Tập tin test1.el:

;;; test1.el ---                                   

;;; Code:

(defvar test-var1)

(defun test-fun1 (test)
  nil)

(require 'test2 "./test2.el)

(provide 'test1)
;;; test1.el ends here

Tập tin test2.el:

;;; test2.el ---  

;;; Code:

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

Nếu sau đó tôi chạy:

emacs -batch -f batch-byte-compile *.el

Tôi nhận được kết quả sau:

Compiling .../test1.el...
Wrote .../test1.elc
Compiling .../test2.el...

In test-fun2:
test2.el:9:15:Warning: reference to free variable `test-var1'

In end of data:
test2.el:14:1:Warning: the function `test-fun1' is not known to be defined.
Wrote .../test2.elc

Tôi hiểu tại sao những cảnh báo này xuất hiện và tôi hiểu rằng chúng chỉ là những cảnh báo. Tuy nhiên, thật dễ dàng để bỏ lỡ một lỗi đánh máy trong tên hàm bằng cách loại bỏ tất cả các cảnh báo thuộc loại này.

Tôi bằng cách nào đó nghĩ rằng thêm một (require 'test2)dòng trong test2.elnên sửa nó. Tuy nhiên, trong trường hợp này tôi nhận được:

Compiling .../test1.el...

In toplevel form:
test1.el:10:1:Error: Recursive `require' for feature `test2'
Compiling .../test2.el...

In toplevel form:
test2.el:5:1:Error: Recursive `require' for feature `test1'

Điều này là khó hiểu, bởi vì tôi nghĩ rằng điểm requirechính xác là để tránh tải đệ quy. Tôi cho rằng đó requirelà hành xử như loadtrong thời gian biên dịch.

Một cách tốt (và an toàn) để thoát khỏi những cảnh báo này là gì?

Hướng dẫn đưa ra cách giải quyết (tôi đăng nó dưới dạng câu trả lời tốt hơn không có gì bên dưới), nhưng cuối cùng, tôi muốn giải pháp khá tự động (không yêu cầu tôi liệt kê tất cả các hàm và biến mà tôi sẽ cần mỗi tập tin).

Giải pháp lý tưởng sẽ được tích hợp sẵn trong emacs hoặc được cung cấp với Cask. Nếu nó không tồn tại, tôi sẽ lấy những gì có sẵn trong khóa học.

Câu trả lời:


8

Về yêu cầu

requirekhông có nghĩa là để tránh tải lặp đi lặp lại , nó có nghĩa là để tránh tải lặp đi lặp lại . Vì vậy, không, nó không giải quyết vấn đề của bạn ở đây.

Về vấn đề

Cách đúng đắn để tiếp cận điều này (theo ý kiến ​​của tôi) sẽ là tránh sự phụ thuộc lẫn nhau.

Các test1tập tin trong ví dụ của bạn không có lý do để yêu cầu test2. Ngay cả khi điều đó không đúng với gói thực tế của bạn, có thể bạn có thể thiết kế lại cách bạn ủy quyền mã giữa các tệp. Đó là nói chung có thể để tránh sự phụ thuộc lẫn nhau giữa các tập tin của bạn.

Công việc xung quanh

  1. Nếu không thể tránh được sự phụ thuộc lẫn nhau, hướng dẫn sẽ đề cập đến một giải pháp. Bạn sẽ phải thêm các dòng như sau cho mỗi chức năng / biến bạn cần.

    (declare-function test-fun1 "./test1.el")
    (defvar test-var1)
    
  2. Một lựa chọn khác là requirecác tập tin chỉ có điều kiện. Thêm một cái gì đó như thế này vào tập tin 1:

    (defvar test1-is-loading t)
    (unless (and (boundp 'test2-is-loading)
                 test2-is-loading)
      (require 'test2))
    

    Và một cái gì đó như thế này để nộp 2:

    (defvar test2-is-loading t)
    (unless (and (boundp 'test1-is-loading)
                 test1-is-loading)
      (require 'test1))
    

Tình huống thực tế là một gói trong đó tệp chính khai báo một số nội dung (các biến như bản đồ chế độ, đường dẫn thực thi, nhóm tùy chỉnh), yêu cầu các tệp khác cung cấp một loạt các hàm cho gói và tập hợp tất cả lại với nhau (ít nhất là thành một -modechức năng). Ví dụ này là một ví dụ về đồ chơi, việc sử dụng nó test-fun2sẽ không thay đổi vấn đề gì cả.
T. Verron

1
@ T.Verron Có, tôi đã làm việc với giả định rằng ví dụ của bạn không hoàn toàn chính xác và tôi đã đưa ra cách giải quyết. Tuy nhiên, tôi vẫn tuyên bố rằng gói có thể được thiết kế tốt hơn. Trong ví dụ thực tế của bạn, không có lý do tại sao tệp tập hợp mọi thứ lại với nhau cũng phải là một trong những định nghĩa các biến và nhóm. Tạo một tệp bổ sung có chứa các định nghĩa này (chẳng hạn như test-variables) và tệp này sẽ không phải yêu cầu bất kỳ định nghĩa nào khác.
Malabarba

Như tôi đã đề cập trong câu trả lời ban đầu của tôi, cách giải quyết đầu tiên của bạn hoạt động tốt, nhưng nó khá tẻ nhạt. Điều thứ hai nghe có vẻ hứa hẹn, nhưng sẽ phức tạp hơn một chút: các câu lệnh sẽ cần được gói trong eval-when-compilevà các gói sẽ cần đặt biến của chúng nilở cuối tệp (vì tất cả các tệp được biên dịch trong một phiên duy nhất). Nó cũng có lợi ích cho tôi thấy tại sao tải đệ quy chính xác lại phức tạp hơn để tránh hơn tải lặp lại.
T. Verron

Và cảm ơn cho đề nghị tái cấu trúc, điều này thực sự sẽ làm việc.
T. Verron

4

Ví dụ của bạn thật kỳ lạ:

  • Bạn requiretest2 ở cuối test1, trong khi đó require"luôn luôn" ở đầu tập tin.
  • Test1 của bạn không gọi bất kỳ hàm test2 nào, vì vậy nó không cần test2 để hoạt động (do đó require, nó không cần thiết) và OTOH test2 của bạn không gọi các hàm test1, vì vậy nó không cần test1, nhưng nó không thành công require.

IOW, bạn có requirelạc hậu của bạn .


Trong một ví dụ thực tế, test1tất nhiên sẽ sử dụng các hàm được xác định bởi test2và gói sẽ chỉ được tải thông qua test1(thông qua tự động tải). Không nên trả lời này là một nhận xét thay thế? Nó chỉ chỉ ra rằng ví dụ của tôi được lựa chọn khá kém, và không cung cấp câu trả lời cho câu hỏi.
T. Verron

@ T.Verron Câu trả lời này đã trả lời trường hợp cụ thể mà bạn cung cấp. Thực tế đó là một ví dụ được lựa chọn kém không phải là lỗi của anh ấy. ;-)
Malabarba

Vì cả hai câu trả lời đều đề cập đến điểm tái cấu trúc này, tôi phải cho rằng đó là một điểm hợp lệ cho câu hỏi. Bạn có lợi ích trong việc giải quyết các vấn đề chung, cộng với các gợi ý trong nhận xét cho trường hợp cụ thể của tôi. Đây có phải là một trong những thực sự có ích cho bất cứ ai có một usecase thực sự?
T. Verron

Và với một cái nhìn ngắn hạn hơn, nếu nó đã được đăng dưới dạng một bình luận, tôi có thể đã chỉnh sửa câu hỏi để làm cho nó (hơi) thực tế hơn. Nhưng nó được đăng dưới dạng câu trả lời, vì vậy tôi không thể chỉnh sửa câu hỏi mà không biến nó thành một câu hỏi khác (khác vì câu trả lời trước sẽ không còn được áp dụng). Imo, câu trả lời này rơi chính xác trong lĩnh vực bình luận: yêu cầu làm rõ (trong trường hợp này là một xác nhận rằng ví dụ tối thiểu thực sự là đại diện của vấn đề) từ người hỏi.
T. Verron

3

Các nhãn hiệu gợi ý thêm declare-functiondefvardòng.

Tệp kết quả test2là:

;;; test2.el ---  

;;; Code:

(declare-function test-fun1 "./test1.el")
(defvar test-var1)

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

Tuy nhiên, điều này cần phải được thực hiện cho tất cả các chức năng và tất cả các biến được xác định trong tệp "cha".

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.