私はClojureを初めて使い、Compojureを使用して基本的なWebアプリケーションを作成しています。しかし、Compojureのdefroutes
構文で壁にぶつかっているので、その背後にある「方法」と「理由」の両方を理解する必要があると思います。
リングスタイルのアプリケーションは、HTTPリクエストマップで始まり、一連のミドルウェア関数を介してリクエストを応答マップに変換し、それをブラウザに送り返すように見えるようです。このスタイルは開発者にとっては「低レベル」すぎるため、Compojureのようなツールが必要です。私は、他のソフトウェアエコシステムでも、特にPythonのWSGIを使用して、より多くの抽象化が必要であることがわかります。
問題は、Compojureのアプローチが理解できないことです。次のdefroutes
S式を見てみましょう。
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
このすべてを理解するための鍵はマクロブードゥー教徒の中にあることは知っていますが、マクロを完全には理解していません(まだ)。私は長い間defroutes
ソースをじっと見つめてきましたが、取得しないでください!何が起きてる? 「大きなアイデア」を理解することは、おそらくこれらの特定の質問に答えるのに役立つでしょう。
workbench
関数)からリング環境にアクセスするにはどうすればよいですか?たとえば、HTTP_ACCEPTヘッダーまたはリクエスト/ミドルウェアの他の部分にアクセスしたいとしますか?{form-params :form-params}
)?分解するときに使用できるキーワードは何ですか?私はClojureが本当に好きですが、私はとても困惑しています!
booleanknot.com にJames Reeves(Compojureの著者)から優れた記事があり、それを読んで「クリック」してくれたので、ここでその一部を書き直しました(これで終わりです) )。
この正確な質問に答えるスライドデッキ ここでは同じ著者による もあります。
Compojureは、http要求の抽象化である Ring に基づいています。
A concise syntax for generating Ring handlers.
それで、それらは何ですか Ring handlers ?ドキュメントから抽出:
;; Handlers are functions that define your web application.
;; They take one argument, a map representing a HTTP request,
;; and return a map representing the HTTP response.
;; Let's take a look at an example:
(defn what-is-my-ip [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)})
非常に単純ですが、非常に低レベルでもあります。上記のハンドラーは、 ring/util
ライブラリ。
(use 'ring.util.response)
(defn handler [request]
(response "Hello World"))
次に、リクエストに応じて異なるハンドラーを呼び出します。次のような静的ルーティングを行うことができます。
(defn handler [request]
(or
(if (= (:uri request) "/a") (response "Alpha"))
(if (= (:uri request) "/b") (response "Beta"))))
そして、このようにリファクタリングします:
(defn a-route [request]
(if (= (:uri request) "/a") (response "Alpha")))
(defn b-route [request]
(if (= (:uri request) "/b") (response "Beta"))))
(defn handler [request]
(or (a-route request)
(b-route request)))
ジェームズが次に指摘する興味深いことは、「2つ以上のルートを組み合わせた結果はそれ自体がルートであるため」、ルートをネストできることです。
(defn ab-routes [request]
(or (a-route request)
(b-route request)))
(defn cd-routes [request]
(or (c-route request)
(d-route request)))
(defn handler [request]
(or (ab-routes request)
(cd-routes request)))
今では、マクロを使用して、ファクタリングできるように見えるコードをいくつか見始めています。 Compojureはdefroutes
マクロを提供します:
(defroutes ab-routes a-route b-route)
;; is identical to
(def ab-routes (routes a-route b-route))
Compojureは、GET
マクロなどの他のマクロを提供します。
(GET "/a" [] "Alpha")
;; will expand to
(fn [request#]
(if (and (= (:request-method request#) ~http-method)
(= (:uri request#) ~uri))
(let [~bindings request#]
~@body)))
最後に生成された関数は、ハンドラーのように見えます!
James post を確認してください。詳細な説明が記載されています。
ルートで何が起こっているのかを見つけるのにまだ苦労している人にとっては、私のように、あなたは破壊のアイデアを理解していないかもしれません。
実際に let
のドキュメント を読んで、「魔法の価値はどこから来たのか」という全体を明らかにするのに役立ちました。質問。
以下の関連セクションを貼り付けます。
Clojureは、letバインディングリスト、fnパラメーターリスト、およびletまたはfnに展開されるマクロで、しばしば構造化と呼ばれる抽象構造バインディングをサポートします。基本的な考え方は、binding-formがinit-exprのそれぞれの部分にバインドされるシンボルを含むデータ構造リテラルになることができるということです。バインディングは抽象的です。ベクトルリテラルはシーケンシャルなものにバインドでき、マップリテラルはアソシエイティブなものにバインドできます。
Vector binding-exprsを使用すると、ベクター、リスト、seq、文字列、配列、nthをサポートするものなど、(ベクターだけでなく)連続したものの一部に名前をバインドできます。基本的なシーケンシャル形式は、nthで検索されたinit-exprからの連続する要素にバインドされるbinding-formsのベクトルです。さらに、オプションで、その後にbinding-formsを指定すると、そのbinding-formがシーケンスの残りの部分、つまり、まだバインドされていない部分にバインドされ、nthnextを介して検索されます。最後に、オプションの:asの後にシンボルが続くと、そのシンボルはinit-expr全体にバインドされます。
(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
[a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]
Vector binding-exprsを使用すると、ベクター、リスト、seq、文字列、配列、nthをサポートするものなど、(ベクターだけでなく)連続したものの一部に名前をバインドできます。基本的なシーケンシャル形式は、nthで検索されたinit-exprからの連続する要素にバインドされるbinding-formsのベクトルです。さらに、オプションで、その後にbinding-formsを指定すると、そのbinding-formがシーケンスの残りの部分、つまり、まだバインドされていない部分にバインドされ、nthnextを介して検索されます。最後に、オプションの:asの後にシンボルが続くと、そのシンボルはinit-expr全体にバインドされます。
(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
[a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]
破壊({form-params:form-params})の対処法は何ですか?分解するときに使用できるキーワードは何ですか?
使用可能なキーは、入力マップにあるキーです。構造化は、letおよびdoseqフォーム内、またはfnまたはdefnのパラメーター内で使用可能です。
次のコードが参考になることを願っています。
(let [{a :thing-a
c :thing-c :as things} {:thing-a 0
:thing-b 1
:thing-c 2}]
[a c (keys things)])
=> [0 2 (:thing-b :thing-a :thing-c)]
ネストされた破壊を示すより高度な例:
user> (let [{thing-id :id
{thing-color :color :as props} :properties} {:id 1
:properties {:shape
"square"
:color
0xffffff}}]
[thing-id thing-color (keys props)])
=> [1 16777215 (:color :shape)]
賢明に使用すると、構造化は定型的なデータアクセスを回避することでコードを整理します。 :asを使用して結果(または結果のキー)を出力すると、他のどのデータにアクセスできるかをよりよく把握できます。