Cách tải lại tệp clojure trong REPL


170

Cách tải lại các chức năng ưa thích được xác định trong tệp Clojure là gì mà không phải khởi động lại REPL. Ngay bây giờ, để sử dụng tập tin cập nhật, tôi phải:

  • biên tập src/foo/bar.clj
  • đóng REPL
  • mở REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Ngoài ra, (use 'foo.bar :reload-all)không dẫn đến hiệu ứng cần thiết, đó là đánh giá các phần thân đã được sửa đổi và trả về các giá trị mới, thay vì hành xử như nguồn không thay đổi gì cả.

Tài liệu:


20
(use 'foo.bar :reload-all)luôn luôn làm việc tốt cho tôi Ngoài ra, (load-file)không bao giờ cần thiết nếu bạn có đường dẫn lớp được thiết lập đúng. "Hiệu ứng bắt buộc" bạn không nhận được là gì?
Dave Ray

Vâng, "hiệu ứng bắt buộc" là gì? Đăng một bar.cljchi tiết mẫu về "hiệu ứng cần thiết".
Sridhar Ratnakumar

1
Theo hiệu ứng bắt buộc, tôi có nghĩa là nếu tôi có một hàm (defn f [] 1)và tôi đã thay đổi định nghĩa của nó thành (defn f [] 2), thì dường như sau khi tôi phát hành (use 'foo.bar :reload-all)và gọi fhàm đó, nó sẽ trả về 2, chứ không phải 1. Thật không may, nó không hoạt động theo cách đó cho tôi và mọi Khi tôi thay đổi phần thân của chức năng, tôi phải khởi động lại REPL.
pkaleta

Bạn phải có một vấn đề khác trong thiết lập của bạn ... :reloadhoặc :reload-allcả hai nên hoạt động.
Jason

Câu trả lời:


196

Hoặc là (use 'your.namespace :reload)


3
:reload-allcũng nên làm việc OP đặc biệt nói là không, nhưng tôi nghĩ có điều gì đó không ổn trong môi trường phát triển của OP vì đối với một tệp duy nhất, hai ( :reload:reload-all) sẽ có tác dụng tương tự. Đây là lệnh đầy đủ cho :reload-all: (use 'your.namespace :reload-all) Điều này cũng tải lại tất cả các phụ thuộc.
Jason

77

Ngoài ra còn có một cách khác như sử dụng tools.namespace , nó khá hiệu quả:

user=> (use '[clojure.tools.namespace.repl :only (refresh)])

user=> (refresh)

:reloading (namespace.app)

:ok

3
câu trả lời này là thích hợp hơn
Bahadir Çambel

12
Hãy cẩn thận: chạy (refresh)dường như cũng khiến REPL quên rằng bạn đã yêu cầu clojure.tools.namespace.repl. Các cuộc gọi tiếp theo (refresh)sẽ cung cấp cho bạn một RuntimeException, "Không thể giải quyết biểu tượng: làm mới trong ngữ cảnh này." Có lẽ điều tốt nhất để làm là (require 'your.namespace :reload-all), hoặc, nếu bạn biết bạn sẽ muốn làm mới REPL của mình rất nhiều cho một dự án nhất định, hãy tạo một :devhồ sơ và thêm [clojure.tools.namespace.repl :refer (refresh refresh-all)]vàodev/user.clj .
Dave Yarwood

1
Blogpost về quy trình làm việc Clojure của tác giả công cụ.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer

61

Tải lại đang Clojure sử dụng (require … :reload):reload-allrất có vấn đề :

  • Nếu bạn sửa đổi hai không gian tên phụ thuộc lẫn nhau, bạn phải nhớ tải lại chúng theo đúng thứ tự để tránh lỗi biên dịch.

  • Nếu bạn xóa định nghĩa khỏi tệp nguồn và sau đó tải lại, các định nghĩa đó vẫn có sẵn trong bộ nhớ. Nếu mã khác phụ thuộc vào các định nghĩa đó, nó sẽ tiếp tục hoạt động nhưng sẽ phá vỡ lần sau khi bạn khởi động lại JVM.

  • Nếu không gian tên được tải lại chứa defmulti, bạn cũng phải tải lại tất cả các defmethodbiểu thức liên quan .

  • Nếu không gian tên được tải lại chứa defprotocol, bạn cũng phải tải lại bất kỳ bản ghi hoặc loại nào thực hiện giao thức đó và thay thế bất kỳ phiên bản hiện có nào của các bản ghi / loại đó bằng các phiên bản mới.

  • Nếu không gian tên được tải lại chứa macro, bạn cũng phải tải lại bất kỳ không gian tên nào sử dụng các macro đó.

  • Nếu chương trình đang chạy chứa các hàm đóng trên các giá trị trong không gian tên được tải lại, các giá trị đóng đó sẽ không được cập nhật. (Điều này là phổ biến trong các ứng dụng web xây dựng "ngăn xếp xử lý" như là một thành phần của các chức năng.)

Thư viện clojure.tools.namespace cải thiện đáng kể tình hình. Nó cung cấp một chức năng làm mới dễ dàng, tải lại thông minh dựa trên biểu đồ phụ thuộc của các không gian tên.

myapp.web=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
myapp.web=> (refresh)
:reloading (myapp.web)
:ok

Thật không may, tải lại lần thứ hai sẽ thất bại nếu không gian tên mà bạn tham chiếu refreshhàm thay đổi. Điều này là do thực tế là tools.namespace phá hủy phiên bản hiện tại của không gian tên trước khi tải mã mới.

myapp.web=> (refresh)

CompilerException java.lang.RuntimeException: Unable to resolve symbol: refresh in this context, compiling:(/private/var/folders/ks/d6qbfg2s6l1bcg6ws_6bq4600000gn/T/form-init819543191440017519.clj:1:1)

Bạn có thể sử dụng tên var đủ điều kiện để khắc phục sự cố này nhưng cá nhân tôi không muốn phải gõ tên đó trên mỗi lần làm mới. Một vấn đề khác ở trên là sau khi tải lại không gian tên chính, các hàm trợ giúp REPL tiêu chuẩn (như docsource) không còn được tham chiếu ở đó nữa.

Để giải quyết các vấn đề này, tôi thích tạo một tệp nguồn thực tế cho không gian tên người dùng để nó có thể được tải lại một cách đáng tin cậy. Tôi đặt tập tin nguồn vào ~/.lein/src/user.cljnhưng bạn có thể đặt ở bất cứ đâu. Tệp phải yêu cầu chức năng làm mới trong khai báo ns hàng đầu như thế này:

(ns user
  (:require [clojure.tools.namespace.repl :refer [refresh]]))

Bạn có thể thiết lập hồ sơ người dùng leiningen trong ~/.lein/profiles.cljvì vậy vị trí mà bạn đặt các tập tin trong được thêm vào đường dẫn lớp. Hồ sơ sẽ trông giống như thế này:

{:user {:dependencies [[org.clojure/tools.namespace "0.2.7"]]
        :repl-options { :init-ns user }
        :source-paths ["/Users/me/.lein/src"]}}

Lưu ý rằng tôi đặt không gian tên người dùng làm điểm vào khi khởi chạy REPL. Điều này đảm bảo rằng các hàm trợ giúp REPL được tham chiếu trong không gian tên người dùng thay vì không gian tên chính của ứng dụng của bạn. Bằng cách đó, họ sẽ không bị mất trừ khi bạn thay đổi tệp nguồn chúng tôi vừa tạo.

Hi vọng điêu nay co ich!


Gợi ý tốt. Một câu hỏi: tại sao mục ": source-path" ở trên?
Alan Thompson

2
@DirkGeurs, với :source-pathstôi nhận được #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, trong khi với :resource-pathsmọi thứ đều ổn.
fl00r

1
@ fl00r và nó vẫn ném lỗi đó? Bạn có dự án hợp lệ.clj trong thư mục bạn đang khởi chạy REPL từ không? Điều đó có thể khắc phục vấn đề của bạn.
Dirk Geurs

1
Vâng, nó là tiêu chuẩn khá, và tất cả đều hoạt động tốt :resource-paths, tôi đang ở trong không gian tên người dùng của mình bên trong thay thế.
fl00r

1
Tôi vừa có một thời gian tuyệt vời khi làm việc với REPL đang nói dối tôi vì reloadvấn đề này. Sau đó, hóa ra mọi thứ tôi nghĩ là không còn hoạt động nữa. Có lẽ ai đó nên khắc phục tình trạng này?
Alper

41

Câu trả lời tốt nhất là:

(require 'my.namespace :reload-all)

Điều này sẽ không chỉ tải lại không gian tên được chỉ định của bạn, mà còn tải lại tất cả các không gian tên phụ thuộc.

Tài liệu:

yêu cầu


2
Đây là câu trả lời duy nhất hoạt động với lein repl, Coljure 1.7.0 và nREPL 0.3.5. Nếu bạn là người mới đến clojure: Không gian tên ( 'my.namespace) được định nghĩa với (ns ...)trong src/... /core.clj, ví dụ.
Aaron Digulla

1
Vấn đề với câu trả lời này là câu hỏi ban đầu đang sử dụng (tệp tải ...), không yêu cầu. Làm thế nào cô ấy có thể thêm: tải lại tất cả vào không gian tên sau khi tải tập tin?
jgomo3

Bởi vì cấu trúc không gian tên như proj.stuff.core phản chiếu cấu trúc tệp trên đĩa như thế src/proj/stuff/core.clj, REPL có thể định vị tệp chính xác và bạn không cần load-file.
Alan Thompson

6

Một lớp lót dựa trên câu trả lời của papachan:

(clojure.tools.namespace.repl/refresh)

5

Tôi sử dụng điều này trong Lighttable (và instarepl tuyệt vời) nhưng nó nên được sử dụng trong các công cụ phát triển khác. Tôi đã gặp vấn đề tương tự với các định nghĩa cũ về các hàm và đa phương thức treo xung quanh sau khi tải lại vì vậy trong quá trình phát triển thay vì khai báo các không gian tên bằng:

(ns my.namespace)

Tôi khai báo không gian tên của tôi như thế này:

(clojure.core/let [s 'my.namespace]
                  (clojure.core/remove-ns s)
                  (clojure.core/in-ns s)
                  (clojure.core/require '[clojure.core])
                  (clojure.core/refer 'clojure.core))

Khá xấu xí nhưng bất cứ khi nào tôi đánh giá lại toàn bộ không gian tên (Cmd-Shift-Enter trong Lighttable để có kết quả instarepl mới của mỗi biểu thức), nó sẽ thổi bay tất cả các định nghĩa cũ và cho tôi một môi trường sạch sẽ. Tôi đã bị vấp ngã vài ngày bởi các định nghĩa cũ trước khi tôi bắt đầu làm điều này và nó đã giúp tôi tỉnh táo. :)


3

Thử tải lại tập tin?

Nếu bạn đang sử dụng IDE, thường có một phím tắt để gửi khối mã đến REPL, do đó xác định lại một cách hiệu quả các chức năng liên quan.


1

Ngay khi (use 'foo.bar)làm việc cho bạn, điều đó có nghĩa là bạn có foo / bar.clj hoặc foo / bar_init. Class trên CLASSPATH của bạn. Bar_init. Class sẽ là phiên bản được biên dịch AOT của bar.clj. Nếu bạn làm thế (use 'foo.bar), tôi không chắc chắn chính xác nếu Clojure thích lớp hơn clj hay ngược lại. Nếu nó thích các tệp lớp và bạn có cả hai tệp, thì rõ ràng việc chỉnh sửa tệp clj và sau đó tải lại không gian tên không có hiệu lực.

BTW: Bạn không cần phải load-filetrướcuse CLASSPATH được đặt đúng.

BTW2: Nếu bạn cần sử dụng load-filevì một lý do, thì bạn chỉ cần làm lại nếu bạn chỉnh sửa tệp.


14
Không chắc chắn tại sao điều này được đánh dấu là câu trả lời chính xác. Nó không trả lời câu hỏi rõ ràng.
AnnanFay

5
Khi ai đó đến với câu hỏi này, tôi không thấy câu trả lời này rất rõ ràng.
ctford
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.