Oktal Literal
Pada satu titik saya sedang membaca dalam matriks yang menggunakan nol di depan untuk mempertahankan baris dan kolom yang tepat. Secara matematis ini benar, karena nol di depan jelas tidak mengubah nilai yang mendasarinya. Upaya untuk menentukan var dengan matriks ini, bagaimanapun, akan gagal secara misterius dengan:
java.lang.NumberFormatException: Invalid number: 08
yang benar-benar membuatku bingung. Alasannya adalah bahwa Clojure memperlakukan nilai integer literal dengan nol di depannya sebagai oktal, dan tidak ada angka 08 dalam oktal.
Saya juga harus menyebutkan bahwa Clojure mendukung nilai heksadesimal Java tradisional melalui awalan 0x . Anda juga dapat menggunakan basis apa pun antara 2 dan 36 dengan menggunakan notasi "basis + r + nilai", seperti 2r101010 atau 36r16 yang merupakan 42 basis sepuluh.
Mencoba mengembalikan literal dalam fungsi literal anonim
Ini bekerja:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
jadi saya yakin ini juga akan berhasil:
(#({%1 %2}) :a 1)
tapi gagal dengan:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
karena makro pembaca # () diperluas ke
(fn [%1 %2] ({%1 %2}))
dengan literal peta yang dibungkus dengan tanda kurung. Karena ini adalah elemen pertama, ini diperlakukan sebagai fungsi (yang sebenarnya adalah peta literal), tetapi tidak ada argumen yang diperlukan (seperti kunci) yang disediakan. Singkatnya, literal fungsi anonim tidak meluas ke
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
sehingga Anda tidak dapat memiliki nilai literal ([],: a, 4,%) sebagai badan fungsi anonim.
Dua solusi telah diberikan di komentar. Brian Carper menyarankan menggunakan konstruktor implementasi urutan (array-map, hash-set, vektor) seperti:
(#(array-map %1 %2) :a 1)
sementara Dan menunjukkan bahwa Anda dapat menggunakan fungsi identitas untuk membuka tanda kurung luar:
(#(identity {%1 %2}) :a 1)
Saran Brian benar-benar membawa saya ke kesalahan saya berikutnya ...
Berpikir bahwa hash-map atau array-map menentukan implementasi peta konkret yang tidak berubah
Pertimbangkan hal berikut:
user> (class (hash-map))
clojure.lang.PersistentArrayMap
user> (class (hash-map :a 1))
clojure.lang.PersistentHashMap
user> (class (assoc (apply array-map (range 2000)) :a :1))
clojure.lang.PersistentHashMap
Meskipun Anda secara umum tidak perlu khawatir tentang implementasi konkret peta Clojure, Anda harus tahu bahwa fungsi yang mengembangkan peta - seperti assoc atau conj - dapat menggunakan PersistentArrayMap dan mengembalikan PersistentHashMap , yang bekerja lebih cepat untuk peta yang lebih besar.
Menggunakan fungsi sebagai titik rekursi daripada sebuah loop untuk menyediakan binding awal
Ketika saya memulai, saya menulis banyak fungsi seperti ini:
; Project Euler #3
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Sebenarnya loop akan lebih ringkas dan idiomatik untuk fungsi khusus ini:
; Elapsed time: 387 msecs
(defn p3 [] {:post [(= % 6857)]}
(loop [i 775147 n 600851475143 times 3]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Perhatikan bahwa saya mengganti argumen kosong, badan fungsi "konstruktor default" (p3 775147 600851475143 3) dengan loop + pengikatan awal. The terulang sekarang rebinds binding lingkaran (bukan parameter fn) dan melompat kembali ke titik rekursi (lingkaran, bukan fn).
Mengacu pada vars "phantom"
Saya berbicara tentang jenis var yang mungkin Anda tentukan menggunakan REPL - selama pemrograman eksplorasi Anda - kemudian tanpa disadari merujuk ke sumber Anda. Semuanya berfungsi dengan baik sampai Anda memuat ulang namespace (mungkin dengan menutup editor Anda) dan kemudian menemukan banyak simbol tak terikat yang direferensikan di seluruh kode Anda. Ini juga sering terjadi saat Anda melakukan refactoring, memindahkan var dari satu namespace ke yang lain.
Memperlakukan pemahaman daftar for seperti perintah for loop
Pada dasarnya Anda membuat daftar malas berdasarkan daftar yang ada daripada hanya melakukan loop terkontrol. Dosis Clojure sebenarnya lebih analog untuk setiap konstruksi perulangan yang penting.
Salah satu contoh perbedaannya adalah kemampuan untuk memfilter elemen mana yang diiterasi menggunakan predikat arbitrer:
user> (for [n '(1 2 3 4) :when (even? n)] n)
(2 4)
user> (for [n '(4 3 2 1) :while (even? n)] n)
(4)
Cara lain mereka berbeda adalah mereka dapat beroperasi pada urutan malas yang tak terbatas:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Mereka juga dapat menangani lebih dari satu ekspresi yang mengikat, mengulang ekspresi paling kanan terlebih dahulu dan bekerja ke kiri:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Ada juga yang tidak istirahat atau terus keluar sebelum waktunya.
Terlalu sering menggunakan struct
Saya berasal dari latar belakang OOPish jadi ketika saya memulai Clojure, otak saya masih berpikir dalam kerangka objek. Saya mendapati diri saya memodelkan semuanya sebagai sebuah struct karena pengelompokan "anggota", betapapun longgar, membuat saya merasa nyaman. Pada kenyataannya, sebagian besar struct harus dianggap sebagai pengoptimalan; Clojure akan membagikan kunci dan beberapa informasi pencarian untuk menghemat memori. Anda dapat lebih mengoptimalkannya dengan menentukan pengakses untuk mempercepat proses pencarian kunci.
Secara keseluruhan Anda tidak mendapatkan apa pun dari penggunaan struct di atas peta kecuali untuk kinerja, jadi kerumitan tambahan mungkin tidak sepadan.
Menggunakan konstruktor BigDecimal tidak berbobot
Saya membutuhkan banyak BigDecimals dan menulis kode jelek seperti ini:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
padahal sebenarnya Clojure mendukung literal BigDecimal dengan menambahkan M ke angka:
(= (BigDecimal. "42.42") 42.42M) ; true
Menggunakan versi manis akan mengurangi banyak penggembungan. Dalam komentar, twils menyebutkan bahwa Anda juga dapat menggunakan fungsi bigdec dan bigint agar lebih eksplisit, namun tetap ringkas.
Menggunakan konversi penamaan paket Java untuk namespace
Ini sebenarnya bukan kesalahan, melainkan sesuatu yang bertentangan dengan struktur idiomatik dan penamaan proyek Clojure yang khas. Proyek Clojure substansial pertama saya memiliki deklarasi namespace - dan struktur folder yang sesuai - seperti ini:
(ns com.14clouds.myapp.repository)
yang membengkak referensi fungsi saya yang memenuhi syarat:
(com.14clouds.myapp.repository/load-by-name "foo")
Untuk memperumit banyak hal, saya menggunakan struktur direktori Maven standar :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
yang lebih kompleks daripada struktur Clojure "standar" dari:
|-- src/
|-- test/
|-- resources/
yang merupakan default dari proyek Leiningen dan Clojure itu sendiri.
Peta menggunakan persamaan Java () daripada Clojure = untuk pencocokan kunci
Awalnya dilaporkan oleh chouser di IRC , penggunaan persamaan Java () ini mengarah ke beberapa hasil yang tidak intuitif:
user> (= (int 1) (long 1))
true
user> ({(int 1) :found} (int 1) :not-found)
:found
user> ({(int 1) :found} (long 1) :not-found)
:not-found
Karena instance Integer dan Long 1 dicetak sama secara default, akan sulit untuk mendeteksi mengapa peta Anda tidak mengembalikan nilai apa pun. Ini terutama benar ketika Anda melewatkan kunci Anda melalui suatu fungsi yang, mungkin tanpa Anda ketahui, menghasilkan nilai long.
Perlu dicatat bahwa menggunakan persamaan Java () daripada Clojure = sangat penting untuk peta agar sesuai dengan antarmuka java.util.Map.
Saya menggunakan Programming Clojure oleh Stuart Halloway, Practical Clojure oleh Luke VanderHart, dan bantuan dari banyak hacker Clojure di IRC dan milis untuk membantu jawaban saya.