วิธีโหลดไฟล์ clojure ใหม่ใน REPL


170

เป็นวิธีที่ต้องการของฟังก์ชั่นการโหลดใหม่ที่กำหนดไว้ในไฟล์ Clojure โดยไม่ต้องรีสตาร์ท REPL ตอนนี้เพื่อที่จะใช้ไฟล์ที่อัปเดตฉันต้อง:

  • แก้ไข src/foo/bar.clj
  • ปิด REPL
  • เปิด REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

นอกจากนี้(use 'foo.bar :reload-all)จะไม่ส่งผลให้เกิดผลตามที่ต้องการซึ่งกำลังประเมินเนื้อความของฟังก์ชั่นที่แก้ไขแล้วและคืนค่าใหม่แทนที่จะทำตัวเป็นแหล่งที่มาไม่ได้เปลี่ยนแปลงเลย

เอกสารอ้างอิง:


20
(use 'foo.bar :reload-all)ทำงานได้ดีสำหรับฉันเสมอ นอกจากนี้(load-file)ไม่ควรจำเป็นถ้าคุณตั้ง classpath ของคุณถูกต้อง "เอฟเฟกต์ที่จำเป็น" ที่คุณไม่ได้รับคืออะไร
เดฟเรย์

ใช่สิ่งที่ "ต้องการผลกระทบ" คืออะไร? โพสต์ตัวอย่างbar.cljรายละเอียดเกี่ยวกับ "ผลที่จำเป็น"
Sridhar Ratnakumar

1
ตามผลที่ต้องการฉันหมายถึงว่าถ้าฉันมีฟังก์ชั่น(defn f [] 1)และฉันเปลี่ยนคำจำกัดความของ(defn f [] 2)มันเป็นฉันดูเหมือนว่าหลังจากที่ฉันออก(use 'foo.bar :reload-all)และเรียกใช้fฟังก์ชั่นมันควรจะกลับ 2 ไม่ใช่ 1 แต่น่าเสียดายที่มันไม่ทำงานสำหรับฉันและทุกคน เวลาที่ฉันเปลี่ยนร่างกายของฟังก์ชันฉันต้องรีสตาร์ท REPL
pkaleta

คุณต้องมีปัญหาอื่นในการตั้งค่าของคุณ ... :reloadหรือ:reload-allควรจะทำงานทั้งคู่
Jason

คำตอบ:


196

หรือ (use 'your.namespace :reload)


3
:reload-allควรทำงานเช่นกัน OP บอกเป็นพิเศษว่ามันไม่ได้ แต่ฉันคิดว่ามีบางอย่างผิดปกติในสภาพแวดล้อม dev ของ OP เพราะไฟล์เดียวสอง ( :reloadและ:reload-all) ควรมีผลเหมือนกัน นี่คือคำสั่งเต็มรูปแบบสำหรับ:reload-all: (use 'your.namespace :reload-all) สิ่งนี้เป็นการรีโหลดการอ้างอิงทั้งหมดเช่นกัน
Jason

77

นอกจากนี้ยังมีทางเลือกอื่นเช่นการใช้tools.namespaceซึ่งค่อนข้างมีประสิทธิภาพ:

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

user=> (refresh)

:reloading (namespace.app)

:ok

3
คำตอบนี้เหมาะสมกว่า
Bahadir Cambel

12
ข้อแม้: ทำงาน(refresh)ดูเหมือนว่าจะยังเป็นสาเหตุ REPL clojure.tools.namespace.replที่จะลืมว่าคุณจำเป็นต้องใช้ การเรียกครั้งต่อไป(refresh)จะให้ RuntimeException "ไม่สามารถแก้ไขสัญลักษณ์: รีเฟรชในบริบทนี้" น่าจะเป็นสิ่งที่ดีที่สุดที่จะทำคือการอย่างใดอย่างหนึ่ง(require 'your.namespace :reload-all)หรือหากคุณรู้ว่าคุณจะต้องการที่จะฟื้นฟู REPL ของคุณมากสำหรับโครงการที่กำหนดให้มี:devรายละเอียดและเพิ่ม[clojure.tools.namespace.repl :refer (refresh refresh-all)]dev/user.cljการ
Dave Yarwood

1
Blogpost เกี่ยวกับขั้นตอนการทำงานของ Clojure โดยผู้เขียน tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer

61

การโหลดรหัส Clojure โดยใช้(require … :reload)และ:reload-allมีปัญหามาก :

  • หากคุณปรับเปลี่ยนสองเนมสเปซซึ่งขึ้นอยู่กับกันและกันคุณต้องจำไว้ว่าให้โหลดซ้ำในลำดับที่ถูกต้องเพื่อหลีกเลี่ยงข้อผิดพลาดในการรวบรวม

  • หากคุณลบคำจำกัดความจากไฟล์ต้นฉบับแล้วโหลดซ้ำคำจำกัดความเหล่านั้นยังคงมีอยู่ในหน่วยความจำ หากรหัสอื่นขึ้นอยู่กับคำจำกัดความเหล่านั้นรหัสนั้นจะยังคงทำงานต่อไป แต่จะหยุดพักในครั้งต่อไปที่คุณรีสตาร์ท JVM

  • หากมีเนมสเปซที่โหลดใหม่defmultiคุณต้องโหลดdefmethodนิพจน์ที่เกี่ยวข้องทั้งหมดอีกครั้ง

  • หากมีเนมสเปซที่โหลดใหม่defprotocolคุณต้องโหลดเร็กคอร์ดหรือชนิดใด ๆ ที่ใช้โพรโทคอลนั้นและแทนที่อินสแตนซ์ที่มีอยู่ของเร็กคอร์ด / ชนิดเหล่านั้นด้วยอินสแตนซ์ใหม่

  • หากเนมสเปซที่โหลดใหม่มีมาโครคุณจะต้องโหลดเนมสเปซอื่น ๆ ที่ใช้มาโครเหล่านั้นด้วย

  • หากโปรแกรมที่กำลังรันอยู่มีฟังก์ชั่นที่ปิดค่าในเนมสเปซที่โหลดใหม่ค่าปิดเหล่านั้นจะไม่ถูกอัพเดต (นี่เป็นเรื่องปกติในเว็บแอปพลิเคชันที่สร้าง "ตัวจัดการสแต็ก" เป็นองค์ประกอบของฟังก์ชั่น)

ไลบรารี clojure.tools.namespace ช่วยปรับปรุงสถานการณ์อย่างมีนัยสำคัญ มันมีฟังก์ชั่นการรีเฟรชที่ง่ายซึ่งจะทำการโหลดซ้ำแบบสมาร์ทตามกราฟการพึ่งพาของเนมสเปซ

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

น่าเสียดายที่การโหลดครั้งที่สองจะล้มเหลวหากเนมสเปซที่คุณอ้างถึงrefreshฟังก์ชั่นเปลี่ยนไป นี่คือความจริงที่ว่า tools.namespace ทำลาย namespace ปัจจุบันก่อนที่จะโหลดรหัสใหม่

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)

คุณสามารถใช้ชื่อ var ที่ผ่านการรับรองเป็นวิธีแก้ปัญหาสำหรับปัญหานี้ แต่โดยส่วนตัวแล้วฉันไม่ต้องการพิมพ์ออกมาในการรีเฟรชแต่ละครั้ง ปัญหาอีกข้อหนึ่งที่กล่าวมาก็คือหลังจากที่โหลดเนมสเปซหลักแล้วฟังก์ชันผู้ช่วย REPL มาตรฐาน (เช่นdocและsource) จะไม่มีการอ้างอิงอีกต่อไป

เพื่อแก้ปัญหาเหล่านี้ฉันต้องการสร้างไฟล์ต้นฉบับจริงสำหรับเนมสเปซผู้ใช้เพื่อให้สามารถโหลดซ้ำได้อย่างน่าเชื่อถือ ฉันใส่ไฟล์ต้นฉบับ~/.lein/src/user.cljแต่คุณสามารถวางได้ทุกที่ ไฟล์ควรต้องการฟังก์ชั่นการรีเฟรชในการประกาศบน ns ดังนี้:

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

คุณสามารถตั้งค่าโปรไฟล์ผู้ใช้ Leiningenใน~/.lein/profiles.cljเพื่อให้สถานที่ที่คุณใส่แฟ้มในจะถูกเพิ่มไปยังเส้นทางการเรียน โปรไฟล์ควรมีลักษณะดังนี้:

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

โปรดทราบว่าฉันตั้งค่าเนมสเปซผู้ใช้เป็นจุดเริ่มต้นเมื่อเรียกใช้ REPL สิ่งนี้ช่วยให้มั่นใจว่าฟังก์ชันตัวช่วย REPL ได้รับการอ้างอิงในเนมสเปซผู้ใช้แทนที่จะเป็นเนมสเปซหลักของแอปพลิเคชันของคุณ ด้วยวิธีนี้พวกเขาจะไม่หลงทางเว้นแต่คุณจะแก้ไขไฟล์ต้นฉบับที่เราเพิ่งสร้างขึ้น

หวังว่านี่จะช่วยได้!


คำแนะนำที่ดี คำถามหนึ่ง: ทำไมรายการ ": source-path" ด้านบน
Alan Thompson

2
@ DirkGeurs กับ:source-pathsฉันได้รับ#<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >ในขณะที่:resource-pathsทุกอย่างก็โอเค
fl00r

1
@ fl00r และมันยังคงพ่นข้อผิดพลาดนั้น? คุณมี project.clj ที่ถูกต้องในโฟลเดอร์ที่คุณเรียกใช้งาน REPL หรือไม่ ที่อาจแก้ไขปัญหาของคุณ
Dirk Geurs

1
ใช่มันเป็นมาตรฐานที่ดีและใช้งานได้ดีทั้งหมด:resource-pathsฉันอยู่ในเนมสเปซผู้ใช้ของฉันภายใน repl
fl00r

1
ฉันมีช่วงเวลาที่ดีในการทำงานกับ REPL ที่โกหกฉันเนื่องจากreloadปัญหานี้ จากนั้นก็เปิดออกทุกสิ่งที่ฉันคิดว่าทำงานไม่ได้อีกต่อไป บางทีใครบางคนควรแก้ไขสถานการณ์นี้?
Alper

41

คำตอบที่ดีที่สุดคือ:

(require 'my.namespace :reload-all)

สิ่งนี้จะไม่เพียง แต่โหลดเนมสเปซที่คุณระบุเท่านั้น แต่จะโหลดเนมสเปซที่ขึ้นต่อกันทั้งหมดอีกด้วย

เอกสารอ้างอิง:

จำเป็นต้อง


2
นี่เป็นคำตอบเดียวที่ทำงานร่วมกับlein replColjure 1.7.0 และ nREPL 0.3.5 หากคุณยังใหม่กับ clojure: namespace ( 'my.namespace) ถูกกำหนดด้วย(ns ...)ในsrc/... /core.cljตัวอย่างเช่น
Aaron Digulla

1
ปัญหาของคำตอบนี้คือคำถามเดิมใช้ (โหลดไฟล์ ... ) ไม่จำเป็นต้องใช้ เธอจะเพิ่ม: reload-all ในเนมสเปซหลังโหลดไฟล์ได้อย่างไร
jgomo3

เพราะโครงสร้าง namespace เช่นproj.stuff.coreกระจกโครงสร้างแฟ้มบนดิสก์เช่นsrc/proj/stuff/core.clj, REPL load-fileสามารถค้นหาไฟล์ที่ถูกต้องและคุณไม่จำเป็นต้อง
Alan Thompson


5

ฉันใช้สิ่งนี้ใน Lighttable (และ instarepl ที่ยอดเยี่ยม) แต่ควรใช้ในเครื่องมือพัฒนาอื่น ๆ ฉันมีปัญหาเดียวกันกับคำจำกัดความเก่าของฟังก์ชั่นและวิธีการที่แขวนอยู่รอบ ๆ หลังจากโหลดซ้ำดังนั้นตอนนี้ในระหว่างการพัฒนาแทนที่จะประกาศเนมสเปซด้วย:

(ns my.namespace)

ฉันประกาศเนมสเปซของฉันเป็นแบบนี้:

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

ค่อนข้างน่าเกลียด แต่เมื่อใดก็ตามที่ฉันประเมินเนมสเปซทั้งหมดอีกครั้ง (Cmd-Shift-Enter ใน Lighttable เพื่อรับผลลัพธ์ใหม่ของการแสดงออกในแต่ละนิพจน์) มันจะทำให้คำจำกัดความเก่าทั้งหมดหมดลงและทำให้ฉันมีสภาพแวดล้อมที่สะอาด ฉันถูกสะดุดทุก ๆ สองสามวันโดยคำจำกัดความเก่าก่อนที่ฉันจะเริ่มทำสิ่งนี้และมันช่วยรักษาสติของฉันไว้ :)


3

ลองโหลดไฟล์อีกครั้ง?

หากคุณใช้ IDE โดยปกติจะมีแป้นพิมพ์ลัดเพื่อส่งบล็อครหัสไปยัง REPL ซึ่งจะเป็นการกำหนดฟังก์ชั่นที่เกี่ยวข้องได้อย่างมีประสิทธิภาพอีกครั้ง


1

ทันทีที่ได้(use 'foo.bar)ผลสำหรับคุณหมายความว่าคุณมี foo / bar.clj หรือ foo / bar_init.class ใน CLASSPATH ของคุณ bar_init.class จะเป็น bar.clj ที่รวบรวมโดย AOT ถ้าคุณทำ(use 'foo.bar)ฉันไม่แน่ใจว่า Clojure ชอบเรียนมากกว่า clj หรือวิธีอื่น ๆ หากต้องการไฟล์คลาสและคุณมีทั้งสองไฟล์แสดงว่าการแก้ไขไฟล์ clj และการโหลดเนมสเปซใหม่นั้นไม่มีผล

BTW: คุณไม่จำเป็นต้องทำload-fileก่อนuseถ้า CLASSPATH ของคุณตั้งค่าไว้ถูกต้อง

BTW2: ถ้าคุณต้องการใช้load-fileด้วยเหตุผลคุณสามารถทำได้อีกครั้งหากคุณแก้ไขไฟล์


14
ไม่แน่ใจว่าทำไมจึงถูกทำเครื่องหมายว่าเป็นคำตอบที่ถูกต้อง ไม่ตอบคำถามอย่างชัดเจน
AnnanFay

5
เมื่อมีคนมาที่คำถามนี้ฉันไม่พบคำตอบที่ชัดเจนมาก
ctford
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.