Clojure簡易まとめ

目次

  1. 文法
    1. 関数
    2. 命名(def)
    3. 一時変数
    4. コレクション
    5. メタデータ
    6. コレクション関数
    7. 条件文
    8. 遅延評価
    9. ループ(再帰処理)
    10. 多態性
    11. JavaAPI呼び出し
      1. Javaクラス初期化
      2. メソッド呼び出し
      3. 静的(static)フィールド/メソッド呼び出し
      4. 関数の引数にJava関数を渡す
      5. 間接参照(method chain的な)
      6. 糖衣構文
      7. Thread (Java API)
      8. File I/O (Java API)
    12. マクロ
    13. ライブラリ利用
      1. clojure
      2. java
      3. 名前空間
  2. 用語とか

文法

関数

  • 関数はfirst-class object
  • "fn", "#"を用いて作成する
    (#はfnのshort-hand)
  • clojureに於いては、fn, #を用いてラムダ式を表す
; ----------------------------------------
; fnを利用
; ----------------------------------------
; (fn [x y] (* x y))
(println ((fn [x y] (* x y)) 1 2))
; => 2
; ----------------------------------------
; #を利用
; ----------------------------------------
; #(* %1 %2)
(println (#(* %1 %2) 1 2))
; => 2
  • %1, %2について short-handで定義した場合の引数は%と番号で表す。
    なお1つ目の引数は%のみでも表せる。
; #(* % %)
(println (# (* % %) 2))
; => 4
  • デフォルト値は多重定義かオプショナル引数で行う
; 多重定義
(defn add
  ([x] (add x 1))
  ([x y] (+ x y)))

; オプショナル引数
(defn add [x & {:keys [y] :or {y 1}}]
  (+ x y))

命名(def)

  • def関数を利用して命名する
; 変数
(def x 100)
; 関数
(def mul (fn [x y] (* x y)))
  • 関数はdefnマクロを使うと楽
; 上記の関数命名は、下記のように書ける
(defn mul [x y] (* x y))

一時変数

; 使い方
(let [x 1, y 1] (println (+ x y))
(let [x 1
      y 1]
  (println (+ x y)))

NOTE
変数の定義時に、横に並べる場合、カンマを入れると見やすくなる。
カンマを入れない場合、自由変数に束縛変数を入れると、[x a y b]のようになったりする。

コレクション

; 線形リスト
'(1 2 3)
; マップ(ハッシュテーブル)
{:x 100 :y 200}
; 集合
#{10 20}
(set '(10 20 10)) ; <=> #{10 20} (集合は要素がユニークになる)
; ベクタ
[1 2 3]

メタデータ

; データ
(def vect [1 2 3 4])
; メタデータ付与
(def vect-with-meta (with-meta vect {:music :be-ok}))
; メタ情報アクセス
(:music (meta vect-with-meta))
; => :be-ok
; (?) (meta vect-with-meta)でメタ情報のマップを作っているのかな?

コレクション関数

ここが参考になると思う(yuwki0131-blog: Clojure(1.3-1.4)のリスト/ベクタ(シーケンス)操作関数一覧)

条件文

  • 条件文はif,when,if-let,when-letなどを使う
; ----------------------------------------
; if関数
; ----------------------------------------
; 基本形
(if condition then-expr else-expr)
; 例
; (then-expr, else-exprは単一Object?のみ指定可能
; 複数処理を行いたい場合はdoで囲む)
(defn even-printer [n]
  (if (= (mod n 2) 0)     ; condition
      (println "Even!")   ; then-expr
      (println "Odd!")))  ; else-expr
(defn even-printer2 [n]   ; 複数処理Ver
  (if (= (mod n 2) 0)
    (println "Even!")
    (do
      (println "Odd!")
      (println "Odd2!"))))
; ----------------------------------------
; when関数 (Else節が不要な場合はこっちを使う)
; ----------------------------------------
; 基本形
(when condition expr1 expr2 ...)
; 例
(defn even-printer [n]    ; condition
  (when (even? n)
    (println "Even1")     ; expr1
    (println "Even2")     ; expr2
    (println "Even3")))   ; expr3
; ----------------------------------------
; if-letマクロ
; ----------------------------------------
(defn p-next [lst]
  (if-let [element (first lst)]
    (println "Exist Element : " element)
    (println "No Elements")))
; ----------------------------------------
; when-letマクロ
; ----------------------------------------
(defn p-next [lst]
  (when-let [element (first lst)]
    (println "First : " element)
    (println "First : " element)))

遅延評価

  • 値が必要になるまで、実際の計算を行わないこと
  • 用語
    • promise : 実際に計算を行われていない中間状態
    • thunk : 計算の実体
    • force : 値の計算(promiseをforceする)
; 無限リスト(正整数の生成)
(defn inf-list
  ([] inf-list 0)
  ([n] (cons n (lazy-seq (inf-list (inc n))))))
; 実行例
(println (inf-list))              ; => (0 1 2 3 ...) Infinity
(println (take 3 (inf-list)))     ; => (0 1 2)
(println (take 3 (inf-list 100))) ; => (100 101 102)

ループ(再起処理)

  • ClojureJVMを使う為、末尾再帰の最適化を行えないらしい
    (通常の再帰呼び出しよりloopマクロを利用する方が、スタックを無駄にしないということか?)
; ----------------------------------------
; Loop利用Ver (推奨)
; ----------------------------------------
(defn fact [n]
  (loop [i n
        result 1]
    (if (zero? i)
      result
      (recur (dec i) (* result i)))))
; ----------------------------------------
; 通常のRecursive (非推奨?)
; ----------------------------------------
(defn fact [n]
  (if (zero? n)
    1
    (* n (fact (dec n)))))

多態性

; ----------------------------------------
; マルチメソッドでcase文的なもの
; ----------------------------------------
(defmulti class-checker class)
(defmethod class-checker Integer [arg] (println "Integer!"))
(defmethod class-checker Double [arg] (println "Double!"))
(defmethod class-checker String [arg] (println "String!"))
(defmethod class-checker :default [arg] (println "Other!"))
; (class-checker 1)           ; => Integer!
; (class-checker 1.0)         ; => Double!
; (class-checker (def x 100)) ; => Other!
; ----------------------------------------
; 他の例
; ----------------------------------------
(defmulti func (fn [x y] [(class x) (class y)]))
(defmethod func [Integer Integer] [x y] (Math/pow x y))
(defmethod func [Double Integer] [x y] (Math/pow x y))
(defmethod func :default [_ _] 'other)
; (println (func 2 10))     ; => 1024.0
; (println (func 2.0 10))   ; => 1024.0
; (println (func "A" "B"))  ; other

JavaAPI呼び出し

; 初期化は"new", "."(マクロ)を用いて行う
(new java.lang.Double "100")
(java.lang.Double. "100")
; java.langはデフォルトでimportされているので、下記でもよい
(new Double "100")
(Double. "100")
; インスタンス作成
(def value1 (Double. 10.0))
(def value2 (Double. 20.0))
(def value3 (Double. 10.0))
; メソッド呼び出しは"."マクロを利用する
; *なお、下記の1つ目の呼び出しは"."の後に空白を入れないこと。
(.compareTo value1 value2)  ; => -1
(. value1 compareTo value3) ; => 0
; "."または"/"を利用する
; static field
(println (. Math PI)) ; => 3.14...
(println Math/PI)     ; => 3.14...
; static method
(println (Double/parseDouble))   ; => 10.0
(println (. Double parseDouble)) ; => 10.0
; "memfn"又は"#"(無名関数)を利用
; memfnを利用する場合
(map (memfn toUpperCase) ["do it" "do it"])
; #を利用する場合
(map #(.toUpperCase %1) ["do it" "do it"])
; Java API専用なので"."を端折れる
(.. (Double/valueOf 10.0) toString length)
; clojureも呼び出し可能なので、JavaAPIを利用した場合は"."を付ける必要がある
(-> (Double/valueOf 10.0) .toString .length)
; 1. Creates an instance
(String. "doit doit")
; 2. Calls a static field/method
Math/PI
(Math/abs -10)
; 3. Calls a method
(.length "DOIT")
; 4. 間接参照
(.. (Double/valueOf 10.0) toString length)
(-> (Double/valueOf 10.0) .toString .length)
(defn delayed-print [ms text]
  (Thread/sleep ms)
  (println text))
(.start (Thread. #(delayed-print 100 "wwwwwww")))
(print "grass")
; ----------------------------------------
; 1. File出力
; ----------------------------------------
(binding [*out* (java.io.FileWriter. "__my.log")]
  (println "YHVH1")
  (println "YHVH2")
  (println "YHVH3")
  (println "YHVH4")
  (println "Lucifer")
  (flush))
; => "__my.log"が作成され、YHVH...が記述される
; ----------------------------------------
; 2. File入力
; ----------------------------------------
(with-open [
  fr (java.io.FileReader. "__my.log")
  br (java.io.BufferedReader. fr)]
  (doseq [line (line-seq br)]
    ; line-seqはbuffer行の遅延シーケンスを返す
    ; doseqはシーケンスに対するループ
    (print line)))

マクロ

ライブラリ利用

; ----------------------------------------
; 1. require関数を利用
; ----------------------------------------
; 基本形
(require 'clojure.string)
; => (clojure.string/join \, ["H" "M"])

; option(as)を利用
(require '[clojure.string :as str])
; => (str/join \, ["H" "M"])

; option(refer)を利用
(require '[clojure.string :refer [join]])
; => (join \, ["H" "M"])

; option(refer)による全関数取得
(require '[clojure.string :refer :all])

; ----------------------------------------
; 2. use関数を利用
; ----------------------------------------
; 非推奨(1.4以降)らしい
; 下記の様に呼び出す
(import '(java.math BigDecimal BigInteger))
(import java.io.InputStream)
(import '(java.util List Set)
        '(java.net URL))
; 利用例
(println (BigDecimal. 100.0))
(println (BigInteger. "10000"))
; ----------------------------------------
; 0. フォルダ構造
; ----------------------------------------
basedir
  ├── lib
  │   ├── another.clj
  │   ├── another_math.clj
  │   └── calc
  │       ├── core.clj
  │       └── core_math.clj
  ├── main.clj
  └── utils.clj
; ----------------------------------------
; 1. main.clj
; ----------------------------------------
#^:shebang '[
  java -cp "$CLOJUREPATH/clojure-1.6.0.jar:./" clojure.main "$0" "$@"
]
; 起動時にクラスパスを適切に設定する
; (今回は同フォルダ以下のファイルを参照するので、カレントディレクトリ(./)をクラスパスを指定する)
; モジュール読み込み
(require 'utils)
(require 'lib.another)
(require 'lib.calc.core)
;
(utils/hello)
(println (lib.calc.core/myadd 1 2))
(println (lib.another/mymul 2 4))

; ----------------------------------------
; 2. utils.clj
; ----------------------------------------
; ここの名前空間はファイル名と一致させておく
(ns utils)
(defn hello [] (println "HELLO"))

; ----------------------------------------
; 3. another.clj
; ----------------------------------------
; フォルダ名と一致させる(lib) &
; 名前空間をファイル名と一致させる(another) &
; 分割したモジュールを読み込む (load "another_math")
(ns lib.another
    (load "another_math")) ; ".clj"の部分は入れない

; ----------------------------------------
; 4. another_math.clj
; ----------------------------------------
(in-ns 'lib.another)  ; ベースとなるモジュールと同じ名前空間にする
(defn mymul [x y] (* x y))

; ----------------------------------------
; 5. calc.clj
; ----------------------------------------
; こっちも同様にする
(ns lib.calc.core
    (load "core_math"))

; ----------------------------------------
; 6. calc_math.clj
; ----------------------------------------
(in-ns 'lib.calc.core)
(defn myadd [x y] (+ x y))

Note(参考)

用語とか

first-class object

生成、代入、演算、受け渡しなどが無制限に使用できる対象の事。 定義としては、以下の能力を持つプログラムの事を言う。

DEF

  1. オブジェクトに名前が付けられる
    (defを使った命名とかかな?)

  2. 引数として関数・手続きに渡す事ができる
    (手続き?)

  3. 結果値として関数から帰ってくる事ができる
    (戻り値を"関数"としてもよいという事)

  4. データ構造に組み込む事ができる
    (データ構造 : class, struct, dict, listなど
    組み込む?)

History

version date memo
0.1 2015/03/21 first create
0.2 2015/03/22 JavaAPI関連追加
0.3 2015/03/25 名前空間,ファイル分割追加
0.4 2016/12/17 文字化け修正

Information

Using(SW etc) version
Ubuntu 12.04
Clojure 1.6.0

MEMO

  • macro調査
  • namespace調査

Note(参考)