Số bát phân theo nghĩa đen
Có lúc tôi đang đọc trong một ma trận sử dụng các số 0 ở đầu để duy trì các hàng và cột thích hợp. Về mặt toán học, điều này đúng, vì số 0 đứng đầu rõ ràng không làm thay đổi giá trị cơ bản. Tuy nhiên, cố gắng xác định một var với ma trận này sẽ thất bại một cách bí ẩn với:
java.lang.NumberFormatException: Invalid number: 08
điều đó hoàn toàn khiến tôi bối rối. Lý do là vì Clojure xử lý các giá trị số nguyên theo nghĩa đen với các số 0 ở đầu là số bát phân và không có số 08 trong hệ bát phân.
Tôi cũng nên đề cập rằng Clojure hỗ trợ các giá trị thập lục phân Java truyền thống thông qua tiền tố 0x . Bạn cũng có thể sử dụng bất kỳ cơ số nào từ 2 đến 36 bằng cách sử dụng ký hiệu "cơ số + r + giá trị", chẳng hạn như 2r101010 hoặc 36r16 là 42 cơ số 10.
Cố gắng trả về các ký tự trong một ký tự hàm ẩn danh
Những công việc này:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
vì vậy tôi tin rằng điều này cũng sẽ hoạt động:
(#({%1 %2}) :a 1)
nhưng nó không thành công với:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
vì macro trình đọc # () được mở rộng thành
(fn [%1 %2] ({%1 %2}))
với bản đồ được bao bọc trong dấu ngoặc đơn. Vì nó là phần tử đầu tiên nên nó được coi như một hàm (thực sự là một bản đồ chữ), nhưng không cung cấp đối số bắt buộc (chẳng hạn như khóa). Tóm lại, nghĩa đen của hàm ẩn danh không mở rộng thành
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
và vì vậy bạn không thể có bất kỳ giá trị chữ nào ([],: a, 4,%) làm phần thân của hàm ẩn danh.
Hai giải pháp đã được đưa ra trong các bình luận. Brian Carper đề xuất sử dụng các hàm tạo triển khai trình tự (bản đồ mảng, bộ băm, vectơ) như sau:
(#(array-map %1 %2) :a 1)
trong khi Dan cho thấy rằng bạn có thể sử dụng hàm nhận dạng để mở ngoặc đơn bên ngoài:
(#(identity {%1 %2}) :a 1)
Đề nghị của Brian thực sự đưa tôi đến sai lầm tiếp theo của tôi ...
Nghĩ rằng bản đồ băm hoặc bản đồ mảng xác định việc triển khai bản đồ cụ thể không thay đổi
Hãy xem xét những điều sau:
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
Trong khi bạn thường sẽ không phải lo lắng về việc thực hiện cụ thể của bản đồ Clojure, bạn nên biết rằng các chức năng đó phát triển một bản đồ - như assoc hoặc conj - có thể mất một PersistentArrayMap và trả về một PersistentHashMap , mà thực hiện nhanh hơn cho bản đồ lớn hơn.
Sử dụng một hàm làm điểm đệ quy thay vì một vòng lặp để cung cấp các ràng buộc ban đầu
Khi tôi bắt đầu, tôi đã viết rất nhiều hàm như thế này:
; 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))))
Trong thực tế, vòng lặp thực tế sẽ ngắn gọn và dễ hiểu hơn cho hàm cụ thể này:
; 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))))
Lưu ý rằng tôi đã thay thế đối số trống, thân hàm "hàm tạo mặc định" (p3 775147 600851475143 3) bằng một vòng lặp + ràng buộc ban đầu. Việc lặp lại bây giờ quay lại các ràng buộc của vòng lặp (thay vì tham số fn) và nhảy trở lại điểm đệ quy (vòng lặp, thay vì fn).
Tham chiếu các vars "ma"
Tôi đang nói về loại var mà bạn có thể xác định bằng REPL - trong quá trình lập trình khám phá của bạn - sau đó tham chiếu vô tình trong nguồn của bạn. Mọi thứ hoạt động tốt cho đến khi bạn tải lại không gian tên (có thể bằng cách đóng trình chỉnh sửa của bạn) và sau đó khám phá ra một loạt các ký hiệu không liên kết được tham chiếu trong mã của bạn. Điều này cũng thường xuyên xảy ra khi bạn đang cấu trúc lại, di chuyển một var từ vùng tên này sang vùng tên khác.
Xử lý việc hiểu danh sách for giống như một vòng lặp for bắt buộc
Về cơ bản, bạn đang tạo một danh sách lười biếng dựa trên các danh sách hiện có thay vì chỉ thực hiện một vòng lặp có kiểm soát. Liều lượng của Clojure thực sự tương tự hơn với các cấu trúc vòng lặp foreach bắt buộc.
Một ví dụ về cách chúng khác nhau là khả năng lọc các yếu tố mà chúng lặp lại bằng cách sử dụng các vị từ tùy ý:
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)
Một cách khác mà chúng khác biệt là chúng có thể hoạt động trên chuỗi lười biếng vô hạn:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Chúng cũng có thể xử lý nhiều hơn một biểu thức ràng buộc, lặp lại biểu thức ngoài cùng bên phải trước và làm việc theo cách sang trái:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Cũng không có nghỉ hoặc tiếp tục thoát sớm.
Lạm dụng quá nhiều cấu trúc
Tôi đến từ một nền tảng OOPish nên khi tôi bắt đầu Clojure, bộ não của tôi vẫn đang suy nghĩ về các đối tượng. Tôi thấy mình đang mô hình hóa mọi thứ như một cấu trúc vì nhóm các "thành viên" của nó, tuy lỏng lẻo, nhưng lại khiến tôi cảm thấy thoải mái. Trong thực tế, cấu trúc chủ yếu nên được coi là một tối ưu hóa; Clojure sẽ chia sẻ chìa khóa và một số thông tin tra cứu để tiết kiệm bộ nhớ. Bạn có thể tiếp tục tối ưu hóa chúng bằng cách định nghĩa accessors để đẩy nhanh quá trình tra cứu chủ chốt.
Nhìn chung, bạn không thu được gì từ việc sử dụng cấu trúc trên bản đồ ngoại trừ hiệu suất, do đó, sự phức tạp thêm vào có thể không đáng.
Sử dụng các hàm tạo BigDecimal không được kiểm soát
Tôi cần rất nhiều BigDecimals và đang viết mã xấu xí như thế này:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
trong khi thực tế, Clojure hỗ trợ các ký tự BigDecimal bằng cách thêm M vào số:
(= (BigDecimal. "42.42") 42.42M) ; true
Sử dụng phiên bản có đường sẽ giúp loại bỏ rất nhiều khối phồng. Trong các nhận xét, twils đã đề cập rằng bạn cũng có thể sử dụng các hàm bigdec và bigint để rõ ràng hơn nhưng vẫn ngắn gọn.
Sử dụng chuyển đổi đặt tên gói Java cho không gian tên
Đây thực sự không phải là một sai lầm, mà là một cái gì đó đi ngược lại cấu trúc thành ngữ và cách đặt tên của một dự án Clojure điển hình. Dự án Clojure quan trọng đầu tiên của tôi có khai báo không gian tên - và cấu trúc thư mục tương ứng - như thế này:
(ns com.14clouds.myapp.repository)
điều này làm tăng các tham chiếu chức năng đủ điều kiện của tôi:
(com.14clouds.myapp.repository/load-by-name "foo")
Để làm mọi thứ phức tạp hơn nữa, tôi đã sử dụng cấu trúc thư mục Maven chuẩn :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
phức tạp hơn cấu trúc Clojure "tiêu chuẩn" của:
|-- src/
|-- test/
|-- resources/
đó là mặc định của các dự án Leiningen và chính Clojure .
Bản đồ sử dụng các hàm equals () của Java thay vì Clojure's = để khớp khóa
Được báo cáo ban đầu bởi chouser trên IRC , việc sử dụng equals () của Java này dẫn đến một số kết quả không trực quan:
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
Vì cả hai phiên bản Số nguyên và Dài của 1 đều được in giống nhau theo mặc định, có thể khó phát hiện tại sao bản đồ của bạn không trả về bất kỳ giá trị nào. Điều này đặc biệt đúng khi bạn chuyển khóa của mình qua một hàm mà có lẽ bạn không biết, trả về lâu.
Cần lưu ý rằng việc sử dụng equals () của Java thay vì = của Clojure là điều cần thiết để bản đồ tuân theo giao diện java.util.Map.
Tôi đang sử dụng Clojure Lập trình của Stuart Halloway, Clojure Thực tế của Luke VanderHart, và sự trợ giúp của vô số tin tặc Clojure trên IRC và danh sách gửi thư để giúp tôi trả lời.