Cara memuat ulang file clojure di REPL

170

Apa cara yang disukai untuk memuat ulang fungsi yang didefinisikan dalam file Clojure tanpa harus me-restart REPL. Saat ini, untuk menggunakan file yang diperbarui saya harus:

  • sunting src/foo/bar.clj
  • tutup REPL
  • buka REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Selain itu, (use 'foo.bar :reload-all)tidak menghasilkan efek yang diperlukan, yang mengevaluasi tubuh fungsi yang dimodifikasi dan mengembalikan nilai baru, alih-alih berperilaku sebagai sumber tidak berubah sama sekali.

Dokumentasi:

pkaleta
sumber
20
(use 'foo.bar :reload-all)selalu bekerja dengan baik untuk saya. Juga, (load-file)seharusnya tidak perlu jika Anda mengatur classpath Anda dengan benar. Apa "efek yang diperlukan" yang tidak Anda dapatkan?
Dave Ray
Ya, apa "efek yang diperlukan"? Posting sampel yang bar.cljmerinci "efek yang diperlukan".
Sridhar Ratnakumar
1
Dengan efek yang diperlukan, saya maksudkan bahwa jika saya memiliki fungsi (defn f [] 1)dan saya mengubah definisi (defn f [] 2), sepertinya bagi saya setelah saya mengeluarkan (use 'foo.bar :reload-all)dan memanggil ffungsi itu harus mengembalikan 2, bukan 1. Sayangnya itu tidak bekerja seperti itu untuk saya dan setiap waktu saya mengubah tubuh fungsi saya harus me-restart REPL.
pkaleta
Anda harus memiliki masalah lain di pengaturan Anda ... :reloadatau :reload-allkeduanya harus bekerja.
Jason

Jawaban:

196

Atau (use 'your.namespace :reload)

Ming
sumber
3
:reload-alljuga harus bekerja. OP secara khusus mengatakan tidak, tapi saya pikir ada sesuatu yang salah di lingkungan dev OP karena untuk satu file keduanya ( :reloaddan :reload-all) harus memiliki efek yang sama. Inilah perintah lengkap untuk :reload-all: (use 'your.namespace :reload-all) Ini memuat ulang semua dependensi juga.
Jason
77

Ada juga alternatif seperti menggunakan tools.namespace , ini cukup efisien:

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

user=> (refresh)

:reloading (namespace.app)

:ok
papachan
sumber
3
jawaban ini lebih tepat
Bahadir Cambel
12
Peringatan: berlari (refresh)tampaknya juga menyebabkan REPL lupa bahwa Anda telah diminta clojure.tools.namespace.repl. Panggilan berikutnya untuk (refresh)akan memberi Anda RuntimeException, "Tidak dapat menyelesaikan simbol: menyegarkan dalam konteks ini." Mungkin hal terbaik untuk dilakukan adalah baik (require 'your.namespace :reload-all), atau, jika Anda tahu Anda akan ingin menyegarkan repl Anda banyak untuk proyek tertentu, membuat :devprofil dan menambahkan [clojure.tools.namespace.repl :refer (refresh refresh-all)]kedev/user.clj .
Dave Yarwood
1
Blogpost pada alur kerja Clojure oleh penulis tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer
61

Reload kode Clojure menggunakan (require … :reload)dan :reload-alladalah sangat bermasalah :

  • Jika Anda memodifikasi dua ruang nama yang saling bergantung, Anda harus ingat untuk memuatnya kembali dalam urutan yang benar untuk menghindari kesalahan kompilasi.

  • Jika Anda menghapus definisi dari file sumber dan kemudian memuatnya kembali, definisi tersebut masih tersedia dalam memori. Jika kode lain tergantung pada definisi-definisi itu, itu akan terus bekerja tetapi akan rusak ketika Anda memulai kembali JVM.

  • Jika memuat ulang namespace mengandung defmulti, Anda juga harus memuat ulang semua defmethodekspresi terkait .

  • Jika memuat ulang namespace berisi defprotocol, Anda juga harus memuat ulang catatan atau jenis yang menerapkan protokol itu dan mengganti setiap contoh yang ada dari catatan / jenis dengan contoh baru.

  • Jika ruang nama yang dimuat ulang berisi makro, Anda juga harus memuat ulang ruang nama yang menggunakan makro itu.

  • Jika program yang sedang berjalan berisi fungsi-fungsi yang menutup nilai-nilai dalam namespace yang dimuat kembali, nilai-nilai yang ditutup tidak diperbarui. (Ini biasa terjadi pada aplikasi web yang membangun "handler stack" sebagai komposisi fungsi.)

Pustaka clojure.tools.namespace meningkatkan situasi secara signifikan. Ini menyediakan fungsi refresh yang mudah yang melakukan reload cerdas berdasarkan grafik dependensi namespaces.

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

Sayangnya memuat ulang waktu kedua akan gagal jika namespace tempat Anda mereferensikan refreshfungsi berubah. Ini karena fakta bahwa tools.namespace menghancurkan versi namespace saat ini sebelum memuat kode baru.

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)

Anda bisa menggunakan nama var yang sepenuhnya memenuhi syarat sebagai solusi untuk masalah ini, tetapi secara pribadi saya lebih suka tidak harus mengetikkan itu pada setiap refresh. Masalah lain dengan hal di atas adalah bahwa setelah memuat kembali namespace utama fungsi pembantu REPL standar (seperti docdan source) tidak lagi dirujuk di sana.

Untuk mengatasi masalah ini, saya lebih suka membuat file sumber aktual untuk namespace pengguna sehingga dapat dimuat ulang dengan andal. Saya memasukkan file sumber ~/.lein/src/user.cljtetapi Anda dapat menempatkan di mana saja. File harus memerlukan fungsi refresh dalam deklarasi ns atas seperti ini:

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

Anda dapat mengatur profil pengguna leiningen di ~/.lein/profiles.cljlokasi yang Anda masukkan file ditambahkan ke jalur kelas. Profil akan terlihat seperti ini:

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

Perhatikan bahwa saya menetapkan namespace pengguna sebagai titik masuk saat meluncurkan REPL. Ini memastikan bahwa fungsi pembantu REPL mendapatkan referensi di namespace pengguna alih-alih namespace utama aplikasi Anda. Dengan begitu mereka tidak akan hilang kecuali Anda mengubah file sumber yang baru saja kita buat.

Semoga ini membantu!

Dirk Geurs
sumber
Saran yang bagus Satu pertanyaan: mengapa entri ": sumber-jalur" di atas?
Alan Thompson
2
@DirkGeurs, dengan :source-pathssaya dapatkan #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, sementara :resource-pathssemuanya baik-baik saja.
fl00r
1
@ fl00r dan masih melempar kesalahan itu? Apakah Anda memiliki project.clj yang valid dalam folder tempat Anda meluncurkan REPL? Itu mungkin memperbaiki masalah Anda.
Dirk Geurs
1
Ya, itu cukup standar, dan semua berfungsi dengan baik :resource-paths, saya di namespace pengguna saya di dalam repl.
fl00r
1
Saya hanya bersenang-senang bekerja dengan REPL yang berbohong kepada saya karena reloadmasalah ini . Kemudian ternyata semua yang saya pikir sedang bekerja sudah tidak ada lagi. Mungkin seseorang harus memperbaiki situasi ini?
Alper
41

Jawaban terbaik adalah:

(require 'my.namespace :reload-all)

Ini tidak hanya akan memuat ulang namespace yang Anda tentukan, tetapi juga akan memuat semua ruang nama dependensi.

Dokumentasi:

memerlukan

Alan Thompson
sumber
2
Ini adalah satu-satunya jawaban yang berhasil lein repl, Coljure 1.7.0 dan nREPL 0.3.5. Jika Anda baru mengenal clojure: Namespace ( 'my.namespace) didefinisikan dengan (ns ...)di src/... /core.clj, misalnya.
Aaron Digulla
1
Masalah dengan jawaban ini adalah bahwa pertanyaan awal menggunakan (memuat file ...), tidak perlu. Bagaimana dia bisa menambahkan: reload-all ke namespace setelah load-file?
jgomo3
Karena struktur namespace seperti proj.stuff.coremirror struktur file pada disk suka src/proj/stuff/core.clj, REPL dapat menemukan file yang benar dan Anda tidak perlu load-file.
Alan Thompson
6

Satu liner berdasarkan jawaban papachan:

(clojure.tools.namespace.repl/refresh)
Jiezhen Yi
sumber
5

Saya menggunakan ini di Lighttable (dan instarepl yang luar biasa) tetapi harus digunakan dalam alat pengembangan lainnya. Saya mengalami masalah yang sama dengan definisi fungsi yang lama dan metode multimetro yang berkeliaran setelah dimuat ulang jadi sekarang selama pengembangan alih-alih mendeklarasikan namespace dengan:

(ns my.namespace)

Saya menyatakan ruang nama saya seperti ini:

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

Cukup jelek, tetapi setiap kali saya mengevaluasi kembali seluruh namespace (Cmd-Shift-Enter di Lighttable untuk mendapatkan hasil instarepl baru dari setiap ekspresi), itu menghapus semua definisi lama dan memberi saya lingkungan yang bersih. Saya tersandung setiap beberapa hari oleh definisi lama sebelum saya mulai melakukan ini dan itu telah menyelamatkan kewarasan saya. :)

optevo
sumber
3

Coba muat file lagi?

Jika Anda menggunakan IDE, biasanya ada pintasan keyboard untuk mengirim blok kode ke REPL, sehingga secara efektif mendefinisikan ulang fungsi terkait.

Paul Lam
sumber
1

Segera setelah (use 'foo.bar)bekerja untuk Anda, itu berarti Anda memiliki foo / bar.clj atau foo / bar_init.class di CLASSPATH Anda. Bar_init.class akan menjadi versi bar.clj yang dikompilasi oleh AOT. Jika Anda melakukannya (use 'foo.bar), saya tidak yakin apakah Clojure lebih suka kelas daripada clj atau sebaliknya. Jika lebih suka file kelas dan Anda memiliki kedua file, maka jelas bahwa mengedit file clj dan kemudian memuat kembali namespace tidak berpengaruh.

BTW: Anda tidak perlu load-filesebelum usejika CLASSPATH Anda diatur dengan benar.

BTW2: Jika Anda perlu menggunakan load-file karena suatu alasan, maka Anda bisa melakukannya lagi jika Anda mengedit file.

Tassilo Horn
sumber
14
Tidak yakin mengapa ini ditandai sebagai jawaban yang benar. Itu tidak menjawab pertanyaan dengan jelas.
AnnanFay
5
Sebagai seseorang yang datang ke pertanyaan ini, saya tidak menemukan jawaban ini sangat jelas.
ctford