[Clojure]
文法
制御構文
- if
- if-elseを書きたい場合はこれがよい
(if true ;; true節 (println "true") ;; false節 (println "false"))
- if-let
- when
- when-let
- case
- cond
- switch文を書きたい場合はこれがよい
- condp
コレクション操作
- Ruby
- 見慣れた形、データが先で関数で回す
# listの中身をトレース [1, 2, 3].each do |n| puts n end # mapの中身をトレース { 1 => "a", 2 => "b", 3 => "c" }.each do |k, v| puts "#{k}, #{v}" end
- Clojure
- Lispなのでデータが後で関数が先
# listの中身をトレース (map #(println %) [1 2 3]) (map #(prn %) [1 2 3]) # mapの中身をトレース(doseqでやる、これはなんかいまいち…) (doseq [[k v] {1 "a" 2 "b" 3 "c"}] (prn k v)) # mapの中身をトレース(reduce-kvでやる、こっちは関数名は良いがinit値(=0)を挟まなきゃいけないのがかっこ悪い) (reduce-kv #(println %2 %3) 0 {1 "a" 2 "b" 3 "c"})
- インデックス付きコレクション操作
# Rubyだと…見慣れたブロック記法 [1, 2, 3].each_with_index do |n,idx| puts "#{idx}:#{n}" end # むう… (doseq [[idx n] (map-indexed vector [1 2 3])] (println (str idx ":" n)))
Javaとの連携・移植
- Java
- JavaでMD5をとってみる
import java.util.Arrays; import java.util.StringJoiner; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class prog { public static void main(String[] args) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bytes = md5.digest("password".getBytes()); StringJoiner joiner = new StringJoiner(""); joiner.setEmptyValue(""); for (byte b : bytes) { joiner.add(String.format("%02x", b)); } // Java8でもbyte[]にはStreamが使えない? // Arrays.stream(bytes).forEach(s -> joiner.add(String.format("%02x", s))); System.out.println(joiner.toString()); } }
- Clojure
- がんばってClojureに移植
(import java.util.Arrays) (import java.util.StringJoiner) (import java.security.MessageDigest) (import java.security.NoSuchAlgorithmException) ; letでMessageDigestのインスタンスをmd5に束縛 (let [md5 (MessageDigest/getInstance "MD5") joiner (new StringJoiner "")] (. joiner setEmptyValue "") ; ここで map を使うと遅延評価されてうまくいかない (doseq [eb (. md5 digest(.getBytes "password"))] (. joiner add(format "%02x" eb))) (println (. joiner toString)))
- Clojure - クラスインスタンスのメソッドを呼ぶ
- (. obj method) の形式、引数が無い時は (.method obj) でもいけるっぽい
- Clojure - クラスメソッド(staticメソッド)を呼ぶ
- (Class/method) の形式
ビルド
- requireをちゃんと書いてやらないと、依存関係の解決ができてないように見える
- gen-classでJavaのクラスを生成しているときは、gen-classより先にrequireを書くのがよい
イディオム
アルゴリズム用
木の操作
- 木構造の操作を行うときは clojure.zip というものがうまく使えそうだ
- API clojure.zip
;; HTML用のベクタ型木構造を作成してみる (def html [:html [:title "Title"] [:body]]) => #'user/html (require '[clojure.zip :as zip]) => nil ;; rootの場所を取得 (def root-loc (zip/vector-zip html)) => #'user/root-loc ;; ノードの移動 html => [:html [:title "Title"] [:body]] (zip/node (zip/down root-loc)) => :html (-> root-loc zip/down zip/right zip/node) => [:title "Title"] (-> root-loc zip/down zip/right zip/right zip/node) => [:body] (-> root-loc zip/node) => [:html [:title "Title"] [:body]] (-> root-loc zip/children) => (:html [:title "Title"] [:body])
- 木を巡回してk->vの値変更
- ご丁寧に pre-walk, post-walk がある
https://clojure.github.io/clojure/clojure.walk-api.html
子のあるVectorを順にみていく
;; HTMLデータ (def html [:html [:title "Title"] [:body [:p "Body"]]]) ;; 再帰 (defn recursive-print [src depth] (doseq [[idx item] (map-indexed vector src)] (do (clojure.pprint/pprint item) (if-not (vector? item) (println (str depth " = " item)) (recursive-print item (+ 1 depth))) (- 1 depth)))) ;; 結果 (recursive-print html 0) :html 0 = :html [:title "Title"] :title 1 = :title "Title" 1 = Title [:body [:p "Body"]] :body 1 = :body [:p "Body"] :p 2 = :p "Body" 2 = Body