リテラル8進数
ある時点で、適切な行と列を維持するために先行ゼロを使用する行列を読んでいました。数学的にはこれは正しいです。なぜなら、先行ゼロは明らかに基礎となる値を変更しないからです。ただし、この行列を使用して変数を定義しようとすると、次のように不思議なことに失敗します。
java.lang.NumberFormatException: Invalid number: 08
それは私を完全に困惑させました。その理由は、Clojureが先行ゼロを含むリテラル整数値を8進数として扱い、8進数に08が存在しないためです。
Clojureは0xプレフィックスを介して従来のJava 16進値をサポートすることにも言及する必要があります。「base + r + value」表記を使用して、2から36までの任意の基数を使用することもできます(2r101010または42rである36r16など)。
無名関数リテラルでリテラルを返そうとしています
これは機能します:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
だから私はこれもうまくいくと信じていました:
(#({%1 %2}) :a 1)
しかし、それは失敗します:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
ため#()リーダマクロに展開されます
(fn [%1 %2] ({%1 %2}))
括弧で囲まれたマップリテラル。これは最初の要素であるため、関数(実際にはリテラルマップと同じ)として扱われますが、必須の引数(キーなど)は提供されません。要約すると、匿名関数リテラルんではないに拡大します
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
したがって、無名関数の本体としてリテラル値([] 、: a、4、%)を持つことはできません。
コメントには2つの解決策が示されています。Brian Carperは、次のようにシーケンス実装コンストラクター(配列マップ、ハッシュセット、ベクトル)を使用することを推奨しています。
(#(array-map %1 %2) :a 1)
しばらくダンはあなたが使用できることを示してアイデンティティ外括弧のラップを解除する機能を:
(#(identity {%1 %2}) :a 1)
ブライアンの提案は実際に私を次の間違いに導きます...
ハッシュマップまたは配列マップが不変の具体的なマップ実装を決定することを考える
以下を検討してください。
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
以下のような-あなたは、一般的にClojureのマップの具体的な実装を心配する必要はありませんが、あなたはマップを育てる機能することを知っておくべき連想またはCONJは -取ることができPersistentArrayMapをして返すPersistentHashMapを、これを実行するより速く、より大きなマップの。
ループではなく関数を再帰ポイントとして使用して初期バインディングを提供する
私が始めたとき、私はこのような多くの関数を書きました:
; 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))))
実際には、ループはこの特定の関数に対してより簡潔で慣用的でした。
; 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))))
空の引数である「デフォルトのコンストラクタ」関数本体(p3 775147 600851475143 3)をループ+初期バインディングで置き換えたことに注意してください。これで、recurは(fnパラメータではなく)ループバインディングを再バインドし、再帰ポイント(fnではなくループ)にジャンプします。
「ファントム」変数の参照
私は、探索的プログラミング中にREPLを使用して定義する可能性のあるvarのタイプについて話しており、無意識のうちにソースで参照しています。名前空間をリロードするまで(おそらくエディターを閉じることにより)、後でコード全体で参照されるバインドされていないシンボルの束を発見するまで、すべてが正常に機能します。これは、リファクタリングしているときにも頻繁に発生し、varをあるネームスペースから別のネームスペースに移動します。
for リストの理解を命令的なforループのように扱う
基本的に、単に制御されたループを実行するのではなく、既存のリストに基づいて遅延リストを作成しています。Clojureのdoseqは、実際には命令型foreachループ構造に類似しています。
それらの違いの1つの例は、任意の述語を使用して反復する要素をフィルターする機能です。
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)
もう1つの違いは、無限のレイジーシーケンスを操作できることです。
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
また、複数のバインディング式を処理することもできます。最初に右端の式を繰り返し、左方向に処理します。
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
また、何もありません破るか、引き続き早期に終了します。
構造体の過剰使用
私はOOPishの出身なので、Clojureを始めたとき、私の脳はまだオブジェクトのことを考えていました。「メンバー」のグループ化は、どのように緩くても快適に感じられるため、すべてを構造体としてモデル化することに気づきました。実際には、構造体は主に最適化と見なされます。Clojureは、メモリを節約するためにキーといくつかのルックアップ情報を共有します。キールックアッププロセスを高速化するアクセサーを定義することで、それらをさらに最適化できます。
全体的に、パフォーマンスを除いて、マップ上で構造体を使用しても何も得られないため、追加された複雑さは価値がないかもしれません。
無糖のBigDecimalコンストラクターの使用
私はたくさんのBigDecimalを必要とし、次のような醜いコードを書いていました:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
実際、Clojureは、数値にMを追加することでBigDecimalリテラルをサポートします。
(= (BigDecimal. "42.42") 42.42M) ; true
砂糖漬けバージョンを使用すると、膨満感が大幅に削減されます。コメントの中で、twilsは、bigdec関数とbigint関数を使用して、より明示的でありながら、簡潔さを保つこともできると述べました。
名前空間のJavaパッケージ命名変換の使用
これは実際には間違いではなく、典型的なClojureプロジェクトの慣用的な構造と命名に反するものです。私の最初の実質的なClojureプロジェクトには、次のような名前空間宣言と対応するフォルダー構造がありました。
(ns com.14clouds.myapp.repository)
これは私の完全修飾関数参照を膨らませました:
(com.14clouds.myapp.repository/load-by-name "foo")
さらに複雑にするために、標準のMavenディレクトリ構造を使用しました。
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
これは、「標準」のClojure構造よりも複雑です。
|-- src/
|-- test/
|-- resources/
これは、ライニンゲンプロジェクトとClojure自体のデフォルトです。
マップは、キーの照合にClojureの=ではなくJavaのequals()を利用します
もともとによって報告chouser上のIRC、Javaのこの使い方のequals()いくつかの直感的な結果につながります:
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
1のIntegerインスタンスとLongインスタンスの両方がデフォルトで同じように印刷されるため、マップが値を返さない理由を検出するのが難しい場合があります。これは、おそらくあなたには知られていないがlongを返す関数を介してキーを渡す場合に特に当てはまります。
マップがjava.util.Mapインターフェースに準拠するためには、Clojureの=の代わりにJavaのequals()を使用することが不可欠であることに注意してください。
私はStuart HallowayによるProgramming Clojure、Luke VanderHartによるPractical Clojure、そしてIRCの無数のClojureハッカーの助けと私の回答に役立つメーリングリストを使用しています。