Enliveをさわってみたのでメモ
Enliveをさわってみたのでメモ。
導入
- project.clj の :dependencies に下記を追加
[enlive "1.1.5"]
REPLから試用
user=> (use 'net.cgrand.enlive-html) nil user=> (dir net.cgrand.enlive-html) (中略) html html-content html-resource html-snippet (中略) select (以下略)
html関数
user=> (doc html) ------------------------- net.cgrand.enlive-html/html ([& nodes-specs]) Allows to define inline fragments with a hiccup-like syntax. nil user=> (html [:div {:id "divHoge"} [:input {:id "textHoge" :type "text"}]]) ({:tag :div, :attrs {:id "divHoge"}, :content ({:tag :input, :attrs {:type "text", :id "textHoge"}, :content ()})})
html-resource関数
user=> (doc html-resource) ------------------------- net.cgrand.enlive-html/html-resource ([resource] [resource options]) Loads an HTML resource, returns a seq of nodes. nil user=> ; clojure.java.io の reader 関数を用いて HTML resource を取得して渡してみる user=> (use 'clojure.java.io) nil user=> (doc reader) ------------------------- clojure.java.io/reader ([x & opts]) Attempts to coerce its argument into an open java.io.Read Default implementations always return a java.io.Buffered Default implementations are provided for Reader, Buffere InputStream, File, URI, URL, Socket, byte arrays, charac and String. If argument is a String, it tries to resolve it first as as a local file name. URIs with a 'file' protocol are c local file names. Should be used inside with-open to ensure the Reader is closed. nil user=> (reader "http://google.com") #<BufferedReader java.io.BufferedReader@1969526c> user=> (html-resource (reader "http://google.com")) ({:type :dtd, :data ["html" nil nil]} {:tag :html, :attrs { (以下略)
select関数
user=> (doc select) ------------------------- net.cgrand.enlive-html/select ([node-or-nodes selector]) Returns the seq of nodes or fragments matched by the specified selector. nil user=> (select #_=> (html [:div {:id "divHoge"} [:input {:id "textHoge" :type "text"}]]) #_=> [:input]) ({:tag :input, :attrs {:type "text", :id "textHoge"}, :content ()}) user=> (select #_=> (html [:div {:id "divHoge"} [:input {:id "textHoge" :type "text"}] [:input {:id "textToge" :type "check"}]]) #_=> [:input#textToge]) ({:tag :input, :attrs {:type "check", :id "textToge"}, :content ()}) user=> (select #_=> (html [:html [:div {:id "divHoge"} [:input {:id "textHoge" :type "text"}]] [:input {:id "textToge" :type "check"}]]) #_=> [:div :input]) ({:tag :input, :attrs {:type "text", :id "textHoge"}, :content ()}) user=> (select #_=> (html [:html [:div {:id "divHoge"} [:input {:id "textHoge" :type "text"}]] [:input {:id "textToge" :type "check"}]]) #_=> #{[:div :input] [:#textToge]}) ({:tag :input, :attrs {:type "text", :id "textHoge"}, :content ()} {:tag :input, :attrs {:type "check", :id "textToge"}, :content ()})
実用
強引感が半端ないが、Yahoo!ファイナンスから板気配を取得するコードを書いてみた。
(use 'net.cgrand.enlive-html) (use 'clojure.java.io) (defn kehai-url [meigara-code] (str "http://stocks.finance.yahoo.co.jp/stocks/detail/?code=" meigara-code "." "T")) (defn kehai [meigara-code] (let [[title-row row1 row2] (select (html-resource (reader (kehai-url meigara-code))) [:.main2colL :table :tr])] {:meigara-code meigara-code :uri-kehai (first (:content (first (select row1 [[:td (nth-of-type 1)]])))) :uri-kehai-kabuka (first (:content (first (select row1 [[:td (nth-of-type 3)] :strong])))) :kai-kehai (first (:content (first (select row2 [[:td (nth-of-type 5)]])))) :kai-kehai-kabuka (first (:content (first (select row2 [[:td (nth-of-type 3)] :strong]))))}))
user=> (map kehai [4689 4755]) ({:meigara-code 4689, :uri-kehai "---", :uri-kehai-kabuka "---", :kai-kehai "---", :kai-kehai-kabuka "---"} {:meigara-code 4755, :uri-kehai "---", :uri-kehai-kabuka "---", :kai-kehai "---", :kai-kehai-kabuka "---"})
Clojureでステートマシン書いてみたのでメモ
おもちゃ未満ながら、Clojureでステートマシン書いてみたのでメモ。
(2013/12/16 微編集)
コード
(def states-def "状態定義" {:state01 ;状態ID [[#(println "->state01")] ;上記状態に到達した際に評価される関数のベクタ {:event01 ;イベントID [[#(println "event01 happened!")];上記状態で上記イベントが発生した際に評価される関数のベクタ :state02]}] ;次の状態のID :state02 [[#(println "->state02")] {:event02 [[#(println "event02 happened!")] :state01]}]}) (defn happen-event! "イベント発生" [state event & param] (let [[event-acts next-state] (event (second (state states-def))) next-state-acts (first (next-state states-def))] (dorun (map #(%) event-acts)) (dorun (map #(%) next-state-acts)) next-state))
実行例
state.core=> (happen-event! (happen-event! :state01 :event01) :event02) event01 happened! ->state02 event02 happened! ->state01 :state01 state.core=> (-> :state01 (happen-event! :event01) (happen-event! :event02)) event01 happened! ->state02 event02 happened! ->state01 :state01
マクロについてメモ
Lispのマクロについて、書籍の抜粋メモ。
マクロの使いドコロ
On Lisp 8.1 他の手段では不可能なとき の項より
マクロにできて関数にできないことは2つある:
- まずマクロは引数の評価を制御(または抑制)できること
- また呼び出し側のコンテキスト内へ直に展開されること
だ。結局マクロが必要なアプリケーションには、これらの性質の片方または両方が必要なのだ。
LOL 2.3 制御構造 の項より
「関数ではできない時だけマクロを使う」という言葉は、
- ある引数を評価しないか
- 違う順番で評価をするか
- 二度以上評価する
ような定義をしたければ、どんな定義でもマクロを使う必要があるだろう、
という意味である。
LOL 2.4 自由変数 の項より
事実、いったんそれに慣れてしまうと、できる限りいつでもマクロを書いてレキシカルコンテキストを拡張しようとし、関数を使うのは引数を評価する必要がある時か、単に怖気づいたか、新しいレキシカルコンテキストが欲しい時だけになるLispプログラマもいるくらいである。
LOL 2.7 構文の二重性 の項より
この二重構文を使えば、単一で共通のインタフェースを持ちながら、ダイナミックコンテキストとレキシカルコンテキストの両方で便利な展開を行うマクロを書ける。
On Lisp 8.3 マクロの応用例 の項より
マクロが一般にどう使われるかについては、「主に構文変換に使われる」と言えば一番近いだろう。
LOL 4.4 macroletを使ったコードウォーク
macroletはCOMMON LISPのコードウォーカーとコミュニケーションするためのものであり、マクロの展開がいつ行われるかについては、コンパイル済み関数の実行時より前に完了することだけが保証される。
マクロの使い方
参考文献
- 作者: ポールグレアム,野田開,Paul Graham
- 出版社/メーカー: オーム社
- 発売日: 2007/03/01
- メディア: 単行本
- 購入: 10人 クリック: 146回
- この商品を含むブログ (128件) を見る
- 作者: ダグホイト,Doug Hoyte,タイムインターメディアHOPプロジェクト
- 出版社/メーカー: エスアイビーアクセス
- 発売日: 2009/07/01
- メディア: 単行本
- 購入: 9人 クリック: 73回
- この商品を含むブログ (28件) を見る
Luminusを触ってみたのでメモ
Luminusを触ってみたのでメモ。
概要
- 軽量ライブラリをベースにした小さなWEBアプリケーションフレームワーク
- 以下を狙いとする
- 堅牢
- スケーラブル
- プラットフォームの利用を簡単に
試用
Leiningen テンプレートLuminus-Templateを利用してテンプレートプロジェクトを作成。
なお、今回利用する Leiningen のバージョンは 2.1.3。
lein new luminus myapp cd myapp lein ring server
依存ライブラリ
上記で作成すると、project.clj(抜粋)は↓の感じに。
:dependencies [[org.clojure/clojure "1.5.1"] [lib-noir "0.7.1"] [compojure "1.1.5"] [ring-server "0.3.0"] [selmer "0.4.3"] [com.taoensso/timbre "2.6.2"] [com.postspectacular/rotor "0.1.0"] [com.taoensso/tower "1.7.1"] [markdown-clj "0.9.33"]] :plugins [[lein-ring "0.8.7"]]
Ring, Compojure, lib-noir はいいとして、他を簡単に調べてみた。
生成されたソースの構成
src └─myapp │ handler.clj …defines the base routes for the application, this is the entry point into the applicationand any pages │ repl.clj …provides functions to start and stop the application from the REPL │ util.clj │ ├─routes …routes and controllers for our homepage are located │ home.clj …a namespace that defines the home and about pages of the application │ └─views │ layout.clj …a namespace for the layout helpers │ └─templates base.html …the base layout for the site home.html about.html
誤解を恐れず描くなら、リクエストの流れは概ね↓のイメージだろう。
チュートリアルをやってみる
Your first applicationに沿ってやってみる。
テンプレートを編集して、HOMEページをメッセージボードに置き換えるという内容。
プロジェクト新規作成
lein new luminus guestbook +h2
(H2の組み込みデータベースエンジンをサポートするテンプレートを作成)
(補足)+h2 オプションによって増えた依存ライブラリ
[com.h2database/h2 "1.3.172"] [korma "0.3.0-RC5"] [log4j "1.2.17" :exclusions [javax.mail/mail javax.jms/jms com.sun.jdmk/jmxtools com.sun.jmx/jmxri]]]
(補足)生成されたソースの構成
src │ ★+h2オプションによって増えた部分★ │ log4j.xml …logging configuration for Korma │ └─guestbook │ handler.clj │ repl.clj │ util.clj │ ├─models ★+h2オプションによって増えた部分★ │ db.clj …used to house the functions for interacting with the database │ schema.clj …used to define the connection parameters and the database tables │ ├─routes │ home.clj │ └─views │ layout.clj │ └─templates base.html home.html about.html
DB接続情報の編集
schema.clj の db-spec および db-store を編集する。
なお、create-tables関数が呼ばれたタイミングで、site.db.h2.dbファイルが resources フォルダ直下に生成される。
(def db-store "site.db") (def db-spec {:classname "org.h2.Driver" :subprotocol "h2" :subname (str (io/resource-path) db-store) :user "sa" :password "" :naming {:keys clojure.string/lower-case :fields clojure.string/upper-case}})
テーブル生成関数の編集
schema.clj に create-guestbook-table 関数を追加し、create-tables から呼び出すように編集する。
(defn create-guestbook-table [] (sql/with-connection db-spec (sql/create-table :guestbook [:id "INTEGER PRIMARY KEY AUTO_INCREMENT"] [:timestamp :timestamp] [:name "varchar(30)"] [:message "varchar(200)"]) (sql/do-commands "CREATE INDEX timestamp_index ON guestbook (timestamp)")))
(defn create-tables "creates the database tables used by the application" [] (create-guestbook-table))
DBアクセス関数の編集
db.cljに以下を追記。
(defentity guestbook) (defn save-message [name message] (insert guestbook (values {:name name :message message :timestamp (new java.util.Date)}))) (defn get-messages [] (select guestbook))
アプリケーションの初期化処理の編集(DB初期化処理を追加)
handler.cljの関数 init(アプリケーション開始時に一度だけ呼ばれる関数)に、DB初期化処理(テーブル生成関数の呼び出し)を追加する。
requireに以下を追加。
[guestbook.models.schema :as schema]
init関数内の set-config! に続けて、以下を追加。
;;initialize the database if needed (if-not (schema/initialized?) (schema/create-tables))
コントローラの編集
home.clj の home-page 関数の定義を以下の通り置き換え(ゲストブック表示用のパラメータを追加)。
;(defn home-page [] ; (layout/render ; "home.html" {:content (util/md->html "/md/docs.md")})) (defn home-page [& [name message error]] (layout/render "home.html" {:error error :name name :message message :messages (db/get-messages)}))
require に以下を追加。
[guestbook.models.db :as db]
また、ゲストブックへのメッセージ登録用のコントローラを追加する。
(defn save-message [name message] (cond (empty? name) (home-page name message "Somebody forgot to leave a name") (empty? message) (home-page name message "Don't you have something to say?") :else (do (db/save-message name message) (home-page))))
home-routes の定義に以下を追加する。
(POST "/" [name message] (save-message name message))
HTMLテンプレートの編集
home.html を以下の内容に置き換える。
{% extends "guestbook/views/templates/base.html" %} {% block content %} <ul> {% for item in messages %} <li> <time>{{item.timestamp|date:"yyyy-MM-dd HH:mm"}}</time> <p>{{item.message}}</p> <p> - {{item.name}}</p> </li> {% endfor %} </ul> {% if error %} <p>{{error}}</p> {% endif %} <form action="/" method="POST"> <p> Name: <input type="text" name="name" value={{name}}> </p> <p> Message: <textarea rows="4" cols="50" name="message"> {{message}} </textarea> </p> <input type="submit" value="comment"> </form> {% endblock %}
レイアウトを整える
resources/public/css/screen.css に以下を追記。
form { width: 200px; clear: both; } form input { width: 50%; clear: both; }
パッケージング
- 実行可能jar
lein ring uberjar java -jar target/guestbook-0.1.0-SNAPSHOT-standalone.jar
- war
lein ring uberwar
Clojure開発用Eclipseプラグインメモ その2
以前の日記「Clojure開発用Eclipseプラグインメモ - oknknicの日記」では軽くしか触れてなかったので、もう少し具体的な内容をメモ。
予想してたよりかなり高機能で、Eclipseでの開発を本命にしちゃいそうな気分になってきた。
Counterclockwiseプラグイン
Counterclockwiseの本日(2013/08/17)時点の最新安定版は0.12.0のよう。
導入
Eclipse
上記指定のとおり、Eclipse Juno(4.2)を導入。
(Eclipse IDE for Java EE Developers Juno Service Release 2)
プロジェクトの作成
1. New > Other > Clojure > Clojure Project
2. "Leiningen Template to use" には、プロジェクトのテンプレートを指定。例えば、
"default" なら通常のClojureプロジェクト
"compojure" ならCompojureによるWEBアプリプロジェクト
実行(通常のプロジェクトの場合)
ファイルを右クリック>Run As>Clojure Application で、該当ソースを名前空間としたREPLを起動できる。(なお、プロジェクトフォルダを右クリックした場合は、名前空間はuserになる)
エディタ内で右クリック>Clojure>"Load File in REPL" で、編集後のソースを読み込むことも可能。
また、S式を選択してCtrl-Enter(右記でも同じ:右クリック>Clojure>"Evaluate current selection or top-level form in the visible REPL")でS式単位の評価も可能。
(カーソル位置の最も外側のS式。範囲選択している場合は、その範囲内のみを評価)
実行(Compojureプロジェクトの場合)
別途cmdから実行しても良いが、
以下のとおり外部ツールとして「lein ring server」を登録しておいてもよいだろう。
1. Run > External Tools > External Tools Configurations
2. 「Program」を選択し、「New」を押下
3. 「Main」タブにて以下のとおり設定して「Apply」を押下
Location: lein.batのフルパス(例:C:\lein\lein.bat)
Working Directory:「Browse Workspace」を押下して上記プロジェクトを指定
Arguments: ring server
4. Run > External Tools > Organize Favorites
5. 「Add」から、上記で登録した外部ツールを登録する
コードアシスト
上記に記載のとおり、Clojureのシンボルも java もアシストされる。
java は .(ドット)始まりで入力したときにアシストされる。
ただし、java は(型指定がないため)アシスト候補が膨大なため、少々遅い。
<20013/08/22追記>
なお、コードアシストやタグジャンプ、ドキュメント表示機能等は、
REPLを起動していてかつその名前空間から参照(再帰的な参照も含む)しているものに対してのみ可能なよう。(挙動から推察する限りは)
そのため、コーディングする際は、core.cljの右クリックからREPLを起動した状態で行うのが良さそう。
Clojure対応のIDE Light Table を使ってみた
リアルタイムに評価・おしゃれに結果表示してくれるClojure対応のIDEである Light Table を使ってみた。
アルファ版(2013/08/17時点のバージョンは0.4.11)とのことで、今後の展開にも期待。
REPL起動
command から「Instarepl: Open a clojure instarepl」
(なお、右上に表示されている「live」ボタンのON/OFFで、リアルタイム評価の有効無効の切り替えが可能)
現在開いているエディタをREPLにする
command から「Instarepl: Make current editor an instarepl」
エディタでのキーバインド
C-Enter S式単位の評価
C-Shift-Enter ファイル全体の評価
TAB オートコンプリート
キーバインド一覧の確認
command から「Settings: Change key bindings/shortcuts」を選択
ClojureScriptの評価もできる
Clojureで単体テストメモ〜clojure.test と Midje〜
Clojureでの単体テストについてメモ。
ざっと、以下の選択肢があるよう。
- clojure.test 標準テストライブラリ
- Midje テストフレームワーク。可読性の高いテスト記述をサポートする
- test.generative テストデータ生成、テスト実行ライブラリ。期待値ではなく関数の性質を記述し、乱数引数に対する結果がその性質を満たすかをテストする
今回は、 clojure.test と Midje についてメモ。
clojure.test
利用例
- テスト対象(src/testapp/core.clj)
(ns testapp.core) (defn square "自乗" [x] (* x x))
- テストコード(test/testapp/core_test.clj)
(ns testapp.core-test (:use clojure.test testapp.core)) ; is 利用の場合 (deftest square-test-example01 (testing "自乗" (is (= 4 (square 2))))) ; are 利用の場合 (deftest square-test-example02 (testing "自乗" (are [y x] (= y (square x)) 1 1 4 2 9 3))) ; testing の階層化も可能 (deftest square-test-example03 (testing "squareは" (testing "引数の自乗を返す" (are [y x] (= y (square x)) 1 1 4 2 9 3)) (testing "正の数を返す" (are [x] (<= 0 (square x)) -1 0 1)) (testing "数字以外が渡された場合はClassCastExceptionを投げる" (are [x] (thrown? java.lang.ClassCastException (square x)) "hoge" [1]))))
Leiningenでテスト実行
lein test
REPLでテスト実行
(ns testapp.core-test) (run-tests)
Midje
「テストコード => 期待値」という直感的な記述ができるのが嬉しい。
導入
- project.clj の :profiles に以下を追加
{:dev {:dependencies [[midje "1.5.1"]]}}
- project.clj の :plugins に以下を追加
[lein-midje "3.0.0"]
利用例
- テストコード(test/testapp/core_test.clj)
(ns testapp.core-test (:use midje.sweet testapp.core)) (facts "square関数" (fact "自乗である" (square 1) => 1 (square 2) => 4))
テスト実行
lein midje