Memisahkan namespace Clojure menjadi beberapa file

91

Apakah mungkin untuk membagi namespace Clojure menjadi beberapa file sumber saat melakukan kompilasi sebelumnya :gen-class? Bagaimana (:main true)dan (defn- ...)ikut bermain?

Muntah
sumber

Jawaban:

138

Gambaran

Tentu Anda bisa, pada kenyataannya clojure.corenamespace itu sendiri dibagi dengan cara ini dan menyediakan model yang baik yang dapat Anda ikuti dengan melihat di src/clj/clojure:

core.clj
core_deftype.clj
core_print.clj
core_proxy.clj
..etc..

Semua file ini berpartisipasi untuk membangun satu clojure.corenamespace.

File Utama

Salah satunya adalah file utama, dinamai sesuai dengan nama namespace sehingga akan ditemukan ketika seseorang menyebutkannya di a :useatau :require. Dalam hal ini file utama adalah clojure/core.clj, dan dimulai dengan nsformulir. Di sinilah Anda harus meletakkan semua konfigurasi namespace Anda, terlepas dari file mana yang mungkin membutuhkannya. Ini biasanya termasuk :gen-classjuga, jadi sesuatu seperti:

(ns my.lib.of.excellence
  (:use [clojure.java.io :as io :only [reader]])
  (:gen-class :main true))

Kemudian di tempat yang tepat di file utama Anda (paling umum semua di bagian akhir) gunakan loaduntuk membawa file pembantu Anda. Di clojure.coredalamnya terlihat seperti ini:

(load "core_proxy")
(load "core_print")
(load "genclass")
(load "core_deftype")
(load "core/protocols")
(load "gvec")

Perhatikan bahwa Anda tidak memerlukan direktori saat ini sebagai awalan, Anda juga tidak memerlukan .cljakhiran.

File pembantu

Setiap file pembantu harus dimulai dengan mendeklarasikan namespace mana yang mereka bantu, tetapi harus melakukannya dengan menggunakan in-nsfungsi tersebut. Jadi untuk contoh namespace di atas, semua file helper akan dimulai dengan:

(in-ns 'my.lib.of.excellence)

Hanya itu yang dibutuhkan.

gen-class

Karena semua file ini membangun satu namespace, setiap fungsi yang Anda tetapkan dapat berada di file utama atau helper mana pun. Ini tentu saja berarti Anda dapat menentukan gen-classfungsi Anda di file apa pun yang Anda inginkan:

(defn -main [& args]
  ...)

Perhatikan bahwa aturan urutan definisi normal Clojure masih berlaku untuk semua fungsi, jadi Anda perlu memastikan bahwa file apa pun yang mendefinisikan suatu fungsi dimuat sebelum Anda mencoba menggunakan fungsi itu.

Vars Pribadi

Anda juga bertanya tentang (defn- foo ...)formulir yang mendefinisikan fungsi namespace-private. Fungsi yang didefinisikan seperti ini dan juga :privatevars lainnya terlihat dari dalam namespace tempat mereka didefinisikan, sehingga file utama dan semua helper akan memiliki akses ke vars pribadi yang ditentukan dalam file mana pun yang dimuat sejauh ini.

Chouser
sumber
3
Sangat bagus, jawaban lengkap! BTW, saya hampir selesai pada lintasan pertama saya melalui The Joy of Clojure . Buku yang bagus!
Ralph
Terima kasih telah membagikan jawaban ini. Apakah masih dianggap praktik yang baik, 2 tahun kemudian? (Saya tahu banyak hal berubah dengan cepat.) Saya melihat bahwa Clojure sendiri masih menggunakan teknik ini.
David J.
9
Sampai saat ini, praktik terbaik masih dilakukan jika Anda yakin ingin beberapa file menghasilkan satu namespace. Namun, itu sendiri mungkin kurang umum sekarang daripada sebelumnya. Alternatifnya mungkin dengan mendefinisikan semua vars publik ns Anda dalam satu file, dan memindahkan semua vars dan fungsi helper ke namespace "implementasi" yang terpisah. Secara teknis, vars dalam impl akan menjadi publik, tetapi ns docstring yang menunjukkan bahwa mereka bukan bagian dari API yang terdokumentasi adalah umum dan seharusnya cukup.
Chouser
1
Apakah kami tahu jika perkakas Clojure umum memiliki masalah dalam memahami ruang nama multi-file? Lein? Boot? Cuka Apel? nREPL? Kibit? Eastwood? Cakupan? Dll ...
Didier A.