読者です 読者をやめる 読者になる 読者になる

Arantium Maestum

プログラミング、囲碁、読書の話題

Clojure Web Development勉強 - Compojure (その2)

ちょっと前回の説明で書いていなかったことを補足。

Compojureで使ったdefroutesマクロとGETnot-foundといった個別のルート作成マクロ・関数の引数と戻り値について。

まず、GETやnot-foundは普通に関数を返す。返された関数のシグネチャは当然「リクエストマップを引数に受け取り、レスポンスマップを戻り値に返す」というハンドラの標準のものだ。

(def main-handler
  (GET "/" [] "Hello Main"))

というようにマクロを実行し、関数を作成して特定の名前に束縛することも当然できる。

(main-handler request)

と関数を実行すると、引数のrequestがマップでrequest-methodがgetでuriが"/"であれば

{:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Hello"}

こんな感じのマップが返ってくる。

ちなみに先ほど「引数のrequestがマップでrequest-methodがgetでuriが"/"であれば」と書いた。実際、引数がマップでこの二つのフィールドが存在しその値が正しければ、他のフィールドが何であれ(むしろ他のフィールドは存在しなくても)GETで作った関数は普通に適用される。REPL上でやってみると

=> (main-handler 
      {:request-method :get
       :uri "/"})

{:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Hello"}

といった按配である。

not-foundの返す関数はさらにゆるくて、どんな引数でも

=> ((not-found "Not Found") {:uri "/random-stuff" :request-method :get})
{:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"}

=> ((not-found "Not Found") {})
{:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"}

=> ((not-found "Not Found") nil)
{:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"}

=> ((not-found "Not Found") "a")
{:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"}

のように、同じ404エラーを返す。引数がマップである必要すらない。

ではdefroutesの方はどうだろう。defroutesの定義に使われる関数は基本的にリクエストマップを受け取ってレスポンスマップを返すわけだが、戻り値に関してdefroutesの方で何らかの制限を設けているだろうか?

こういうルーティングをdefroutesで定義してみる:

(defroutes routes
  (GET "/" [] "Main")
  (fn [req] (if (= (:uri req) "/test1") {:a "b"}))
  (fn [req] (if (= (:uri req) "/test2") {}))
  (fn [req] (if (= (:uri req) "/test3") "b"))
  (fn [req] (if (= (:uri req) "/test4") nil))
  (not-found "Not Found"))

それで:uriを"/test1"から"/test4"まで投げてみると、戻り値はnilでなければ何でもいいことがわかる。nilの場合はマッチしなかったと看做されてさら次の関数(この場合はnot-found)から順次適用されることになる。

とまあGETnot-founddefroutesと全てがかなり薄く単純なデータと関数をベースにラップしている機能だということがわかった。例えばバリデーションなどはやってないに等しい。そしてそれは例えばdefroutesを使っているからといってGETを使う必要は必ずしもなく、逆もまた然り、ということになる。

こういう簡単な関数を、推奨される方法はあるにしろある程度自由に組み合わせていってアプリを作成する、という文化はなかなか好きかもしれない。