2013年12月21日土曜日

ABCLでApache POIを使う

ABCLはJVM上で動作するCommon Lisp処理系なので、Javaのライブラリが利用できます。

(require :abcl-contrib)
(require :abcl-asdf)

(asdf:defsystem apache-poi
  :components ((:mvn "org.apache.poi/poi/3.8")
               (:mvn "org.apache.poi/poi-ooxml/3.8")))

(asdf:load-system 'apache-poi)

(defpackage :test-poi
  (:use :cl :jss))

(in-package :test-poi)

(defun create-9x9 (path)
  (let* ((wb (new 'xssfworkbook))
         (sh (#"createSheet" wb)))
    (dotimes (i 9)
      (#"createRow" sh i))
    (dotimes (i 9)
      (dotimes (j 9)
        (#"setCellValue" (#"createCell" (#"getRow" sh j) i)
                         (format nil "~A" (* (1+ i) (1+ j))))))
    (#"write" wb (new 'fileoutputstream path))))

(create-9x9 "test.xlsx")

2013年12月20日金曜日

ABCLでClojureっぽい記法でJavaと連携


gistに書きました。

ABCLはJVM上で動作するCommon Lisp処理系ですが、Javaの機能を呼び出す方法が面倒くさいかんじなのでClojure風の記法でアクセスできるようにするリーダマクロを書いてみました。


2013年12月3日火曜日

はじめてのアセンブリ(Lisp編)

1 はじめに

( Lisp Advent Calendar 2013 3日目の記事です )
FortranやLispが誕生して50年以上の月日が経過した現在では、 世の大半のプログラムがCやJavaやExcel VBAといった高級言語で作られています。
多くのプログラマにとっては、もはやアセンブリは直接書く必要のないものなのかもしれません。
しかし、(たとえあまり使うことがなくても)Lispを学ぶことで悟りを得られる(らしい)ように、 アセンブリを学ぶことで普段使っているOSやコンパイラやVMが一体何をしているのかを学ぶ手助けとなるのではないかと思います。
低レイヤーもカバーするプログラマを目指して、アセンブリ言語をアセンブラでアセンブルして実行してみましょう。

2 足し算

最初にアセンブラを用意しましょう。ここ(http://ccl.clozure.com/)からダウンロードできます。
現在の最新リリースバージョンは1.9なので、以降の内容は Clozure CL 1.9 (Linux X8664) を前提とします。
アセンブリを書くための作業環境として、パッケージを作成しておきます。
(defpackage asm
  (:use :cl :ccl)
  (:import-from :ccl defx86lapfunction))

(in-package :asm)
まずは2つの整数を加算する関数を書いてみます。
1行ずつ解説もつけてみました。
;; add2という関数を定義します。
;; 引数はaとbで、それぞれレジスタarg_yとarg_zを使って渡します。
;; arg_yとarg_zはそれぞれRDIとRSIの別名です。
(defx86lapfunction add2 ((a arg_y) (b arg_z))
  ;; 引数が2つかどうかチェックします。(lapmacro)
  (check-nargs 2)
  ;; RBPをスタックに積みます(スタックフレーム作成その1)
  (pushq (% rbp))
  ;; RSPをRBPに設定(代入)します(スタックフレーム作成その2)
  (movq (% rsp) (% rbp))
  ;; 引数同士を加算してbに保存します。
  (addq (% a) (% b))
  ;; スタックフレームを復元します(BPをSPにコピー,スタックからpopした値をBPにコピー)
  (leave)
  ;; b(arg_z)に保存した値を戻り値として関数を終了します。
  (single-value-return))

;; 実行します。
(add2 1 2)
;; => 3

3 分岐

条件分岐のサンプルとして、引数が1のときに1を、それ以外の時に0を返す関数を作成します。
アセンブリの条件分岐は、cmp命令やtest命令で比較処理時に設定されるフラグを使った条件付きのジャンプ命令で行います。
(defx86lapfunction one? ((n arg_z))
  (check-nargs 1)
  (pushq (% rbp))
  (movq (% rsp) (% rbp))
  (movq (% n) (% rax))
  ;; 戻り値のデフォルト値として1(1である)を設定します。
  (movq ($ '1) (% arg_z))
  ;; RAXと測値1を比較します。
  (cmpq ($ '1) (% rax))
  ;; RAX==1の時, ENDラベルまでジャンプします。
  (je END)
  ;; (RAX!=1の時) 戻り値に0(1でない)を設定します。
  (movq ($ '0) (% arg_z))
  ;; ENDラベル
  END
  (leave)
  (single-value-return))

;; 実行
(one? 0)
;; => 0
(one? 1)
;; => 1
(one? 2)
;; => 0

4 CPUID

最後にCPUID命令を実行して情報を取得する関数を作成します。
CPUIDはEAXレジスタに取得したい情報の種類を指定して呼び出すと、EAX、EBX、ECX、EDXに情報を格納してくれる命令です。 CPUID(Wikipedia)
引数に取得する情報の種類を取り、4つのレジスタの値を返す関数を作成してみます。
なお、自由に使えるレジスタが少ないっぽいので一旦スタックに積んだりしてます。
(defx86lapfunction cpuid ((operation arg_z))
  ;; 引数の数をチェック
  (check-nargs 1)
  ;; スタックフレーム作成
  (pushq (% rbp))
  (movq (% rsp) (% rbp))
  ;; CPUIDで上書きされてしまうRBXを保存
  (pushq (% rbx))
  ;; CPUIDで取得する情報の種類をRAX(EAX)に保存
  (unbox-fixnum operation rax)
  ;; CPUID命令実行
  (cpuid)
  ;; 呼び出し結果をlispのfixnumに変換
  (box-fixnum rbx rbx)
  ;; 多値で返すためにスタックに積む(2つ目)
  (pushq (% rbx))
  ;; 退避していたRBXの値を復元
  (movq (@ 8 (% rsp)) (% rbx))
  (box-fixnum rax rax)
  ;; 多値で返すためにスタックに積む(1つ目)
  (movq (% rax) (@ 8 (% rsp)))
  (box-fixnum rcx rcx)
  ;; 多値で返すためにスタックに積む(3つ目)
  (pushq (% rcx))
  (box-fixnum rdx rdx)
  ;; 多値で返すためにスタックに積む(4つ目)
  (pushq (% rdx))
  ;; 戻り値の数を設定
  (set-nargs 4)
  ;; 関数を終了して多値を返す
  (jmp-subprim .SPnvalret))
(defun u32->str (n)
  (map 'string
       (lambda (byte) (code-char (ldb byte n)))
       (list (byte 8 0)
             (byte 8 8)
             (byte 8 16)
             (byte 8 24))))

;; 実行
(multiple-value-bind (_ ebx ecx edx) (cpuid 0)
  (format nil "~A~A~A"
          (u32->str ebx)
          (u32->str edx)
          (u32->str ecx)))
;; => "GenuineIntel"
Lisp Advent Calendar 2日目でタグの話題がありましたが、CCLはポインタにタグが付いてるタイプのLispです。 なので、Lisp側から整数の1を渡したつもりでも機械語としてはタグの分(ここでは3bit)ずれた値になってしまいます。 box/unboxはこのずれを補正するための処理で、実際には掛け算やシフトが行われています。

5 おわりに

あせんぶりとりすぷがあわさりさいきょうにみえる

6 メモ書き

  • 測値を指定する際にクオートをつけるとlispのfixnum、付けないと機械語の整数になるっぽい
  • 引数が4つ以上になるとスタックを使わなければならない
  • レジスタの別名は compiler/X86/X8664/x8664-arch.lisp で定義されている
  • (:^ lab) : label address expression
  • sar: Shift Arithmetic Right
  • lea: load effective address
  • leave: high level procedure exit (BPをSPにコピー,スタックからpopした値をBPにコピー)
  • disassembleすると出てくる (lea (@ disp (% rip)) (% fn))は、関数のポインタに関数を表すタグ(7)をつけた値をfnレジスタに設定する処理らしい。この命令のバイト数が7になることを利用しているっぽい。

2013年9月29日日曜日

[Common Lisp] 破壊的な関数

Common Lispには、戻り値を作り出すために元々の構造を破壊する関数があります。

sort、stable-sort を除く破壊的な関数には対応する非破壊的な関数があるので、基本的には非破壊版を利用しておいたほうが良さそうです。

なお、破壊的関数の先頭に付く n は non-consing の略だそうです。

詳しくは 実践Common Lispの12章 あたりを見ると良いでしょう。

非破壊的破壊的概要
sortソート
stable-sort安定ソート
reversenreverseリストの反転
appendnconcリストの結合
revappendnreconcリストの結合
butlastnbutlastリストの末尾以外取得
removedeleteシーケンスの要素削除
remove-ifdelete-if条件に一致する要素削除
remove-if-notdelete-if-not条件に一致しない要素削除
remove-duplicatesdelete-duplicates重複を削除
unionnunion和集合
intersectionnintersection積集合
set-differencenset-difference差集合
set-exclusive-ornset-exclusive-or対称差
string-capitalizenstring-capitalize単語の先頭を大文字、それ以外を小文字に変換
string-downcasenstring-downcase文字列を小文字に変換
string-upcasenstring-upcase文字列を大文字に変換
substnsubst木構造(リスト)の要素を変更
subst-ifnsubst-if木構造(リスト)の要素を変更
subst-if-notnsubst-if-not木構造(リスト)の要素を変更
sublisnsublis木構造(リスト)の要素を変更
substitutensubstituteシーケンスの要素を変更
substitute-ifnsubstitute-ifシーケンスの要素を変更
substitute-if-notnsubstitute-if-notシーケンスの要素を変更

2013年9月16日月曜日

[D]デザインパターン修行:Builderパターン

Builderパターンは複雑なオブジェクトの生成を抽象化するデザインパターン。

  • Builderインターフェース
    • オブジェクトの組み立てに使うメソッドを定義する
  • Directorクラス
    • Builderのメソッドを適切な順序で使ってオブジェクトを組み立てる
  • ConcreteBuilderクラス
    • Builderで定義したメソッドを実装する

BuilderパターンをD言語で書いてみます。

/* builderパターン */

import std.algorithm;
import std.string;
import std.stdio;

interface DocBuilder {
  void makeTitle(string title);
  void addContent(string content);
  string getResult();
}

class HtmlBuilder : DocBuilder {
  string title;
  string[] contents;

  override void makeTitle(string title) {
    this.title = title;
  }

  override void addContent(string content) {
    this.contents ~= content;
  }

  override string getResult() {
    string fmt = "<html><head><title>%s</title></head><body>%s</body></html>";
    string c = reduce!((a, b) => a ~ "<a>%s</a>".format(b))("", contents);
    return fmt.format(title, c);
  }
}

class TextBuilder : DocBuilder {
  string title;
  string[] contents;

  override void makeTitle(string title) {
    this.title = title;
  }

  override void addContent(string content) {
    this.contents ~= content;
  }

  override string getResult() {
    string list = "";
    foreach(s; contents) {
      list ~= "- " ~ s ~ "\n";
    }
    return "* " ~ title ~ "\n" ~ list;
  }  
}

class Director {
  private DocBuilder builder;

  this(DocBuilder builder) {
    this.builder = builder;
  }

  string construct(string title, string[] contents) {
    builder.makeTitle(title);
    foreach(c; contents) {
      builder.addContent(c);
    }
    return builder.getResult();
  }
}

void main() {
  auto d1 = new Director(new HtmlBuilder());
  writeln(d1.construct("AAA", ["hello", "world"]));
  auto d2 = new Director(new TextBuilder());
  writeln(d2.construct("AAA", ["hello", "world"]));
}
> dmd builder.d
> ./builder
<html><head><title>AAA</title></head><body><a>hello</a><a>world</a></body></html>
* AAA
- hello
- world

2013年8月29日木曜日

[Common Lisp]iterateのドライバを新しく定義する

Common Lisp Advent Calendar 2012で iterate というloopマクロに似たイテレーションライブラリが紹介されていました。 (iterateを使ってloopマクロの呪縛から解き放たれる)

loopマクロと異なり拡張ができるようなので、 OOPのデザインパターンに出てくる(has-next/nextを使った)イテレーターパターンっぽいコードでループできるようにしてみました。

;; イテレーターパターンっぽいインターフェースと実装を定義
;; iterator-next メソッドと has-next-p メソッドを実装したクラスを定義する

(ql:quickload :cl-fad)
(ql:quickload :cl-annot)

(defpackage pattern.iterator
  (:use :cl :cl-annot))

(in-package :pattern.iterator)

(enable-annot-syntax)

@export 
(defgeneric has-next-p (obj))

@export
(defgeneric iterator-next (obj))

@export
(defclass <sequence-iterator> ()
  ((seq :initarg :seq)
   (pos :initarg :pos :initform 0)))

(defmethod has-next-p ((obj <sequence-iterator>))
  (with-slots (seq pos) obj
    (< pos (length seq))))

(defmethod iterator-next ((obj <sequence-iterator>))
  (with-slots (seq pos) obj
    (prog1 (elt seq pos)
      (incf pos))))

@export
(defun sequence-iterator (seq)
  (make-instance '<sequence-iterator> :seq seq))

@export
(defclass <directory-walker> ()
  ((rest :initform nil)))

@export
(defun directory-walker (root)
  (make-instance '<directory-walker> :root root))

(defmethod initialize-instance ((obj <directory-walker>) &key (root "./") &allow-other-keys)
  (call-next-method)
  (push (fad:canonical-pathname root) (slot-value obj 'rest)))

(defmethod has-next-p ((obj <directory-walker>))
  (with-slots (rest) obj
    (not (null rest))))

(defmethod iterator-next ((obj <directory-walker>))
  (with-slots (rest) obj
    (let ((path (pop rest)))
      (when (fad:directory-exists-p path)
        (setf rest (nconc (fad:list-directory path) rest)))
      path)))
;; ドライバの実装
;; iterator-nextとhas-next-pを使ったループをiterateに組み込む。

(ql:quickload "iterate")
(ql:quickload :cl-fad)
(in-package :cl-user)

(defpackage test
  (:use :cl :iterate :pattern.iterator))

(in-package :test)

;; IN-ITERATORというキーワードでイテレータパターン風の処理でループできるように拡張
(defmacro-driver (FOR var IN-ITERATOR obj)
  (let ((gitr (gensym))
        (kwd (if generate 'generate 'for)))
    `(progn
       (with ,gitr = ,obj)
       (while (pattern.iterator:has-next-p ,gitr))
       (,kwd ,var next (pattern.iterator:iterator-next ,gitr)))))
(iter (for x in-iterator (pattern.iterator:sequence-iterator '(1 2)))
      (collect x))
;; => (1 2)

;; 以下のディレクトリ構成で実行
;; ./ (/home/kurohuku/tmp)
;;  |- A.txt
;;  |- B
;;  |  |- B1.txt
;;  |  |- B2.lisp
;;  |- C.txt
(iter (for x IN-ITERATOR (directory-walker "./"))
      (collect x))
;; => (#P"" #P"/home/kurohuku/tmp/A.txt" #P"/home/kurohuku/tmp/B/"
;;     #P"/home/kurohuku/tmp/B/B1.txt" #P"/home/kurohuku/tmp/B/B2.lisp"
;;     #P"/home/kurohuku/tmp/C.txt")

(iter (for x IN-ITERATOR (directory-walker "./"))
      (when (string-equal (pathname-type x) "txt")
        (collect x)))
;; => (#P"/home/kurohuku/tmp/A.txt" #P"/home/kurohuku/tmp/B/B1.txt"
;;     #P"/home/kurohuku/tmp/C.txt")

2013年8月6日火曜日

[Clojure]Clojure(Java)でスクリーンショット

java.awt.Robot を用いると簡単にスクリーンショットが撮れるようです。

(import [java.awt Rectangle Robot])

(defn save-rectangle [^Rectangle rect fmt file]
  (-> (Robot.)
      (.createScreenCapture rect)
      (javax.imageio.ImageIO/write fmt file)))

(defn save-screen [fmt file]
  (-> (java.awt.Toolkit/getDefaultToolkit)
      .getScreenSize
      Rectangle.
      (save-rectangle fmt file)))

(save-screen "png" (java.io.File. "tmp.png"))

2013年7月13日土曜日

[C++]メンバ変数を比較してソート

テンプレートを使う練習をかねて、構造体のメンバ変数同士を比較してソートしてみます。

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <string>

using namespace std;

/* メンバ変数同士を比較するためのクラス */
template <typename T, typename Elem>
class MemberComp {
private:
  Elem T::* e;

public:
  MemberComp(Elem T::* e) {
    this->e = e;
  };

  bool operator()(const T &a, const T&b) {
    return a.*e < b.*e;
  };
};

template <typename T, typename Elem, typename Nest>
class NestedMemberComp {
private:
  Nest T::* nest;
  Elem Nest::* e;

public:
  NestedMemberComp(Nest T::* nest, Elem Nest::* e) {
    this->nest = nest;
    this->e = e;
  };

  bool operator()(const T &a, const T&b) {
    return a.*nest.*e < b.*nest.*e;
  };
};

/* インスタンス作成時に型名を省略するための関数 */
template<typename T, typename Elem>
MemberComp<T, Elem> make_comp(Elem T::* e) {
  return MemberComp<T, Elem>(e);
}

template<typename T, typename Elem, typename Nest>
NestedMemberComp<T, Elem, Nest> make_comp(Nest T::* nest, Elem Nest::* e) {
  return NestedMemberComp<T, Elem, Nest>(nest, e);
}

/* 比較対象の構造体 */
struct Data {
  int a;
  double b;
  struct _c {
    int d;
  } c;
};

void print_data(Data *t, int n) {
  for (int i = 0; i < n; ++i) {
    cout << t[i].a << ", "
         << setprecision(2) << setiosflags(ios::fixed) << t[i].b 
         << ", " << t[i].c.d << endl;
  }
}

int main(void) {
  Data target[3] = {{0, 1.5, {3}},
                    {1, 3.0, {2}},
                    {2, 2.0, {1}}};

  print_data(target, 3);

  cout << "b でソート" << endl;
  std::sort(target, target + 3, make_comp(&Data::b));
  print_data(target, 3);

  cout << "c.d でソート" << endl;
  std::sort(target, target + 3, make_comp(&Data::c, &Data::_c::d));
  print_data(target, 3);

  // lambda式を使える場合
  cout << "lambda式を使って b でソート" << endl;
  std::sort(target, target + 3, [](const Data &a, const Data &b) { return a.b < b.b; });
  print_data(target, 3);

  return 0;
}

実行結果

0, 1.50, 3
1, 3.00, 2
2, 2.00, 1
b でソート
0, 1.50, 3
2, 2.00, 1
1, 3.00, 2
c.d でソート
2, 2.00, 1
1, 3.00, 2
0, 1.50, 3
lambda式を使って b でソート
0, 1.50, 3
2, 2.00, 1
1, 3.00, 2

lambda式を使えると楽で良さそう。C++11を使えればなぁ。

2013年7月7日日曜日

[Common Lisp] FTPでファイル転送

最近、複数のサーバーのいくつかのディレクトリにファイルを転送する、という作業が必要になりました。

expect + scp(ftp)なシェルスクリプトが作成されましたが、1ファイルごとに接続を行っていて遅かったので python + ftplib で書き直したりしました。

久々に Common Lisp を書こうと思ったので、ftpでの転送処理を題材にしてみます。

1 ライブラリを探す

さすがにftpを実装しようと試みたりはせず、ライブラリを探します。
quicklisp の system-apropos 関数を使ってそれっぽい名前のライブラリを探してみます。

 CL-USER> (ql:system-apropos "ftp")
#<SYSTEM ftp / cl-ftp-20101006-http / quicklisp 2013-06-15>

cl-ftp を使うことにします。

ファイルパスの処理も行いたいです。
組み込みの関数や cl-fad だとパスネームからファイル名+拡張子を取ってくる方法が分からなかったので、 ファイルパスの処理用のライブラリも探してみます。

(ql:system-apropos "path")
#<SYSTEM cl-paths / cl-vectors-20130312-git / quicklisp 2013-06-15>
#<SYSTEM cl-paths-ttf / cl-vectors-20130312-git / quicklisp 2013-06-15>
#<SYSTEM com.gigamonkeys.pathnames / monkeylib-pathnames-20120208-git / quicklisp 2013-06-15>
#<SYSTEM iolib.pathnames / iolib-0.7.3 / quicklisp 2013-06-15>
#<SYSTEM xpath / plexippus-xpath-20120909-darcs / quicklisp 2013-06-15>

iolib.pathnames を使ってみることにします。

ハッシュテーブルのキーと値に対するループ処理を行いたいです。
loopマクロでハッシュテーブルを使おうとすると記述が面倒なので、ユーティリティマクロを定義します。 が、自分で書くのは面倒なのでライブラリを使います。

Common Lispにはユーティリティライブラリがたくさん存在しますが、 今回は最近登場した Quickutil というユーティリティライブラリを使って、 使いたいユーティリティだけを読み込むことにします。

2 コードを書く

;; ** ライブラリ読み込み
;; quicklispで cl-ftp と iolib.pathnames を取得して読み込む。
(ql:quickload :ftp)
(ql:quickload :iolib.pathnames)

;; quickutilを読み込む。
;; 現時点(2013年7月6日)では、まだ quicklisp に登録されていないので、
;; git cloneしたりアーカイブをダウンロードしたりして
;; quicklisp/local-protects の中に保存しておく。
(ql:quickload :quickutil)

;; quickutil を使って dohash マクロと alist-hash-table 関数を読み込む
(qtlc:utilize-utilities '(:dohash :alist-hash-table))


;; ** 実装
(defun make-target-file-name (source-file target-dir)
  (let* ((fname (iolib.pathnames:file-path-file
                 (iolib.pathnames:file-path source-file)))
         (target-fname
           (iolib.pathnames:merge-file-paths fname target-dir)))
    target-fname))

(defun ftp-copy-file (conn file target-dir)
  (let ((target-fname (make-target-file-name file target-dir)))
    (format t " - copy to: ~A~%" target-fname)
    (ftp:store-file conn
                    file
                    (iolib.pathnames:file-path-namestring target-fname))))

;; Emacs: (put 'qtl:dohash 'common-lisp-indent-function 1)
(defun ftp-copy (remote-host copy-info-map &key port username password)
  (ftp:with-ftp-connection (conn :hostname remote-host
                                 :port port
                                 :passive-ftp-p t
                                 :username username
                                 :password password) 
    (format t "connect to [~A]~%" remote-host)
    (qtl:dohash (dir files copy-info-map)
      (dolist (file files)
        (ftp-copy-file conn file dir)))))

;; (転送先ディレクトリ . (転送するファイル ...))
;; リモートの tmp ディレクトリに
;; ローカルのカレントディレクトリにある 1.txt, 2.txt を転送する設定
(defparameter *copy-info-map*
  (qtl:alist-hash-table
   '(("tmp" "1.txt" "2.txt"))
  :test #'equal))

(defparameter *remote-hosts*
  '("localhost"))

(defun run ()
  (dolist (remote-host *remote-hosts*)
    (ftp-copy remote-host *copy-info-map*
              :port 10000
              :username "test"
              :password "test")))

;;  (run)

なお、作業マシンにはftpサーバーを入れてなかったので、 python の pyftpdlib で適当にでっちあげて実行しました。

2013年6月16日日曜日

NetBSDでEmacsを起動するまで

ベアボーンPCにNetBSDをインストールし、そのまま放置して1ヶ月以上が経過していました。

パスワードすら忘れてしまったNetBSDにEmacsを入れるまでの道筋を忘れないようにメモ。

1 パスワード再設定

とくに使うことなく放置していたせいでパスワードを忘たので、再設定を行いました。

  • シングルユーザーモードで起動する
  • 以下のコマンドでパスワード再設定
# マウント済みファイルシステムの状態を変更することを指示する
> mount -u

# fstabに書かれたとおりにマウントする
> mount -a

# rootのパスワードを再設定する
> passwd

2 DHCP有効化

インストール時にネットワークの設定をやっていなかったようなので、DHCPを有効にしました。

> /etc/rc.d/dhclient start

起動時に有効になるよう、rc.confを書き換えました。

# /etc/rc.confに以下の行を追加
dhclient=YES

3 パッケージ管理システムを入れる

NetBSD では pkgsrc というパッケージ管理システムでソースからビルドするのが正義っぽいので、入れてみました。

バイナリのインストールだけならば、 quickstart に書かれているように PKG_PATH 環境変数にURLを設定して、 pkg_addコマンドを実行すれば良いようです。

ソースからビルドする場合、 pkgssrc を落としてくる必要があるようです。

# cvsコマンドのバイナリを入れる
## uname -s => NetBSD
## uname -m => amd64
## uname -r => 6.0.1
## cut -f '1 2' -d. => デリミタ"." で区切ったフィールドの1要素め+2要素め
## uname -r | cut -f '1 2' -d. => 6.0
> PKG_PATH="http://ftp.NetBSD.org/pub/pkgsrc/packages/$(uname -s)/$(uname -m)/$(uname -r|cut -f '1 2' -d.)/All"
> export PKG_PATH
> pkg_add cvs

# ビルドに必要なコマンドを取得( comp.tgz, text.tgz )
> ftp ftp://ftp.netbsd.org/pub/NetBSD/NetBSD-6.0.1/amd64/binary/sets/
ftp> get comp.tgz
ftp> get text.tgz
ftp> quit
> tar zxvfp comp.tgz
> tar zxvfp text.tgz

# csv を使って pkgsrc を落とす
> cvs -danoncvs@anoncvs.netbsd.org:/cvsroot checkout pkgsrc

# bootstap (必要ない?)
> cd pkgsrc/bootstrap
> ./bootstrap

# make install
> cd pkgsrc/emacs24-nox11
> make install clean

2013年3月10日日曜日

[Clojure]PegDownとJavaFXでMarkdownを表示する

JavaFXにはHTMLをレンダリングしてくれる WebView というコンポーネントがあるようです。

JavaのMarkdownプロセッサ PegDown と WebView を利用して Markdownファイルを画面に表示してみます。

;; leiningenでAOTコンパイルするファイルを指定
:aot [markdown-viewer.App]
;; App.clj
(ns markdown-viewer.App
  (:import javafx.application.Application
           javafx.scene.Scene
           javafx.scene.control.Label
           javafx.scene.web.WebView
           javafx.stage.Stage
           javafx.stage.FileChooser
           javafx.stage.FileChooser$ExtensionFilter
           [org.pegdown PegDownProcessor Extensions])
  (:gen-class
   :extends javafx.application.Application))

(defn md->html [^String source]
  (let [parser (PegDownProcessor. Extensions/ALL)]
    (.markdownToHtml parser source)))

(defn make-md-file-chooser ^FileChooser []
  (let [fc (FileChooser.)]
    (.setTitle fc "select Markdown file")
    (-> fc .getExtensionFilters
        (.add (FileChooser$ExtensionFilter. "Markdown" ["*.md" "*.markdown"])))
    fc))

(defn load-html [^WebView wview ^String html]
  (.loadContent (.getEngine wview) html))

(defn load-md [wview md]
  (load-html wview (md->html md)))

(defn -start [this ^Stage stage]
  (let [wview (WebView.)
        fc (make-md-file-chooser)
        ;; ファイル選択
        file (.showOpenDialog fc stage)]
    (when file
      (.setScene stage (Scene. wview 750 500))
      (load-md wview (slurp file))
      (.show stage))))
;; core.clj
(ns markdown-viewer.core
  (require markdown-viewer.App)
  (:gen-class))

(defn -main [& args]
  (javafx.application.Application/launch markdown_viewer.App args))

2013年3月6日水曜日

[Clojure]動的にライブラリを追加する

nREPL経由でClojureを使っていると、再起動させずにライブラリを読み込みたいと思うことがあります。 pomegranate というライブラリを使うと、動的にライブラリを取得してクラスパスに追加してくれます。

leiningenのprofile.cljやproject.cljにライブラリを追加します。

;; :dependenciesに追加
[com.cemerick/pomegranate "0.0.13"]

READMEにある例は以下のようになっています。

(use '[cemerick.pomegranate :only (add-dependencies)])

;; Maven CentralとClojarsをレポジトリとして指定し、incanterを取得
(add-dependencies
 :coordinates '[[incanter "1.5.0-SNAPSHOT"]]
 ;; proxyも指定可能
 ;; :proxy {:host "x.x.x.x" :port 8080}
 :repositories (merge cemerick.pomegranate.aether/maven-central
                      {"clojars" "http://clojars.org/repo"}))

;; Incanterを使ってみる
(use '[incanter core charts])
(doto (function-plot sin -4 4 :y-label "y" :series-label "sin" :legend true)
  (add-function cos -4 4 :series-label "cos")
  view)

[Clojure]Cloverageでカバレッジ計測

Clojureのカバレッジ計測用ライブラリ Cloverage を使ってみます。

leiningenのプラグインとして利用できるので、profile.cljの:pluginsにプラグインを追加します。

;; ~/.lein/profile.clj の :plugins に追加
[lein-cloverage "1.0.2"]

カバレッジの計測対象となるプロジェクトを作成します。

> lein new cloverage-test

プログラムを作成します。

;; src/cloverage_test/core.clj
(ns cloverage-test.core)

(defn fizzbuzz [n]
  (case (mod n 15)
    0 "fizzbuzz"
    (3 6 9 12) "fizz"
    (5 10) "buzz"
    (str n)))

(defn run [end]
  (mapv fizzbuzz (range 1 (inc end))))

テストコードを作成します。

;; test/cloverage_test/core_test.clj
(ns cloverage-test.core-test
  (:use clojure.test
        cloverage-test.core))

(deftest fizzbuzz-test
  (testing "run"
    (is (= ["1" "2" "fizz"] (run 3)))))

テスト+カバレッジ計測を実行します。(lein cloverage)

 > lein cloverage
Test namespaces:  (cloverage-test.core-test)
Performance warning, cloverage_test/core.clj:3 - case has int tests, but tested expression is not primitive.
Loaded  cloverage-test.core  .
Instrumented namespaces.

Testing cloverage-test.core-test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
Ran tests.
Produced output in /home/kurohuku/project/cloverage-test/target/coverage .
HTML: file:///home/kurohuku/project/cloverage-test/target/coverage/index.html

以下のようなHTMLが出力されます。

テストコードを追加してすべての行が実行するようにしてみます。

;; test/cloverage_test/core_test.clj
(ns cloverage-test.core-test
  (:use clojure.test
        cloverage-test.core))

(deftest fizzbuzz-test
  (testing "run"
    (is (= ["1" "2" "fizz" "4" "buzz"
            "fizz" "7" "8" "fizz" "buzz"
            "11" "fizz" "13" "14" "fizzbuzz"]
           (run 15)))))

2013年3月5日火曜日

[Clojure]LWJGLをでOpenGL

LWJGLでOpenGLの画面を表示してみます。 Javaで書くのと大差無いです。多分。

;; dependenciesに追加
[org.lwjgl/lwjgl "2.7.1"]
[org.lwjgl/lwjgl-util "2.7.1"]
[org.lwjgl/lwjgl-native-platform "2.7.1"]
(import '[org.lwjgl.opengl DisplayMode Display GL11])

(def width 640)
(def height 480)
(def display-mode (DisplayMode. width height))

(defn draw []
  (GL11/glClear GL11/GL_COLOR_BUFFER_BIT)
  (GL11/glBegin GL11/GL_LINES)
  (GL11/glColor3d 1.0 0.0 0.0)
  (GL11/glVertex3d 1.0 1.0 0.0)
  (GL11/glVertex3d (- width 1) (- height 1) 0.0)
  (GL11/glEnd)
  (GL11/glFlush))

(defn start []
  (Display/setDisplayMode display-mode)
  (Display/setTitle "Hello")
  (Display/create)
  (GL11/glEnable GL11/GL_CULL_FACE)
  (GL11/glCullFace GL11/GL_BACK)
  (GL11/glMatrixMode GL11/GL_PROJECTION)
  (GL11/glLoadIdentity)
  (GL11/glOrtho 0 width 0 height 0 1)
  (GL11/glMatrixMode GL11/GL_MODELVIEW)
  (while (not (Display/isCloseRequested))
    (draw)
    (Display/update))
  (Display/destroy))

; (start)

CやC++でOpenGLを書く必要に迫られたら、プロトタイプをClojureで書いてみることもできそうです。

2013年2月19日火曜日

少し前に話題になっていたJavaの脆弱性

先月あたりに話題になっていたJavaの脆弱性について解説した記事を眺めてみたのでメモ。 POCへのリンクもあるので文章で書かれたことがどういうコードになるのかがなんとなくわかりました。

CERT/CC Blog: Anatomy of Java Exploits

自分が理解できた(つもり)のは以下のとおり。

  • 背景
    • Javaには信用できない(untrusted)コードの実行を制限する機能が存在する
      • 一番重要なのは SecurityManager クラス。 System.{set,get}SecurityManagerで操作できる
    • SecurityManagerはデスクトップ環境などでは通常nullに設定されていて、特に実行を制限しない
    • アプレットなどではセキュリティポリシーにしたがってファイルアクセスなどを制限するよう設定されている
      • 許可されていないコードを実行しようとするとSecurityExceptionが発生して終了する

クラスローダやリフレクションAPIなどを信用できないコードから実行することは制限されていますが、 脆弱性を利用するとこれらの制限を回避できてしまうようです。

以下の2つを達成することで、任意のコードを実行できるようになるようです。

  1. sun.org.mozilla.javascript.internal.{Context, GeneratedClassLoader} クラスへのハンドルを取得する
  2. 上記クラスを使いdefineClassで動的にセキュリティ機能を無効化する処理を行うクラスを定義する
    • System.securityManagerをnullにする
  • sun.org.mozilla.javascript.internal.{Context, GeneratedClassLoader} クラス
    • セキュリティ機能によるアクセス制御が行われているがprivate宣言されていない
    • アクセス制御が行われていない com.sun.jmx.mbeanserver.MBeanInstantiator クラスの findClass を経由して取得できる
  • java.lang.invoke.MethodHandles.Lookup クラス
    • 直接クラスローダーを作成しようとするとSecurityManagerに阻止されるので、このクラスを経由する
    • コンストラクタやメソッドのハンドルを取得できる
    • 通常、リフレクションAPIはセキュリティ機能により呼び出し元(caller)が信用できるかどうかを確認している
    • java.lang.invoke.MethodHandles.Lookupは脆弱性により信用できる呼び出し元だと判断されてしまう

2013年2月7日木曜日

[Clojure]clojure.asmでHello World

Clojureはバイトコードを作成するために ASM というライブラリ(Javaバイトコード操作用フレームワーク)を 利用しているようです。

(JVMの)Clojureを利用できる環境では(たぶん)ASMが利用できる(clojure.asm)ので、 勉強がてらこのライブラリを利用してHelloWorldプログラムを作成してみます。

(import '[clojure.asm ClassWriter Opcodes])

(def target-name "Hello")

(def cw (ClassWriter. ClassWriter/COMPUTE_MAXS))

;; public class Hello extends java.lang.Object { ...
(.visit cw
        Opcodes/V1_5       ; バージョン
        Opcodes/ACC_PUBLIC ; アクセス修飾子
        target-name        ; クラス名
        nil                ; シグネチャ
        "java/lang/Object" ; 親クラス
        nil)               ; インターフェース名の配列

;; MethodWriter
;; public static void main(java.lang.String []){ ...
(def mw (.visitMethod cw
                      (bit-or Opcodes/ACC_PUBLIC
                              Opcodes/ACC_STATIC) ; アクセス修飾子
                      "main"                      ; メソッド名
                      "([Ljava/lang/String;)V"    ; ディスクリプタ(引数と戻り値の型)
                      nil                         ; シグネチャ
                      nil))                       ; 例外名の配列

;; メソッドの内容
(.visitCode mw)
(.visitFieldInsn mw
                 Opcodes/GETSTATIC
                 "java/lang/System"
                 "out"
                 "Ljava/io/PrintStream;")
(.visitLdcInsn mw "Hello World!")
(.visitMethodInsn mw
                  Opcodes/INVOKEVIRTUAL
                  "java/io/PrintStream"
                  "println"
                  "(Ljava/lang/String;)V")
(.visitInsn mw Opcodes/RETURN)
(.visitMaxs mw 0 0)
(.visitEnd mw)

(.visitEnd cw)

;; バイトコードを取得
(def bytecode (.toByteArray cw))

;; classファイルを作成
(with-open [out (java.io.FileOutputStream. (str target-name ".class"))]
  (.write out bytecode))

上記のコードを実行すると Hello.class という名前でクラスファイルが作成されるので、 javaコマンドで実行してみます。

> java Hello
Hello World!

javapコマンドでバイトコードを逆アセンブルしてみると、 Clojureで書いたコードと対応してるように見えます。

javap -c Hello.class 
public class Hello {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #14                 // String Hello World!
       5: invokevirtual #20                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return        
}

Clojureのコンパイル時の動作を詳しく知りたい場合、このライブラリを理解しておいたほうが良さそうです。

2013年2月6日水曜日

[Clojure]文字列からReaderを作る

文字列を直接指定することはできないので、 getBytes メソッドで配列を取得して ByteArrayInputStreamを作成することで、指定した文字列を読み込むことのできるReaderを作成できます。

(->> (.getBytes "hoge")
     java.io.ByteArrayInputStream.
     java.io.InputStreamReader.
     java.io.BufferedReader.
     .readLine)
;; => "hoge"

(with-open [r (clojure.java.io/reader (.getBytes "Hello\nWorld\n"))]
  (list (.readLine r)
        (.readLine r)))
;; => ("Hello" "World")

2013年2月4日月曜日

[Clojure]リフレクションを使ってインターフェースの関係を図にしてみる

clojure.langあたりのコードを少し読もうとしてみましたが、各クラスやインターフェースの関係が分かりにくいので 図にしてみようと思いました。

clojure.reflectを使ってインターフェースの継承関係を取得し、DOTファイルを作って図にしてみます。

(require '[clojure.reflect :as r])

(defn class-found? [klass]
  (try (Class/forName (.getName klass))
       (catch Exception _ nil)
       (catch Error _ nil)))

(defn clj-class-name [klass]
  (let [name (.getName klass)]
    (if-let [clj-name (re-find #"clojure\.lang\.(.+)" name)]
      (nth clj-name 1)
      (.getName klass))))

(defn clj-class-name? [name]
  (.startsWith name "clojure.lang"))

(defn class-name-list []
  (let [f (doto (.getDeclaredField ClassLoader "classes")
            (.setAccessible true))
        loader (.getClassLoader clojure.lang.RT)]
    (try
      (for [klass (vec (.get f loader))
            :when (class-found? klass)]
        (.getName klass))
      (finally
        (.setAccessible f false)))))

(defn clojure-interface-list []
  (->> (class-name-list)
       (filter clj-class-name?)
       (map #(Class/forName %))
       (filter #(.isInterface %))))

(defn parent-class-set [klass]
  (:bases (r/reflect klass)))

(defn print-interface-tree []
  (printf "digraph \"interface-tree\" {\n")
  (doseq [klass (clojure-interface-list)]
    (let [name (clj-class-name klass)]
      (doseq [parent (parent-class-set klass)]
        (printf "\"%s\" -> \"%s\";\n" name (clj-class-name parent)))))
  (printf "}\n"))

;; (print-interface-tree)

print-interface-tree 関数を実行すると repl に DOTファイルの内容が出力されるので、 DOTファイルとして保存します。

DOTファイルをPNG画像にするには以下のコマンドを実行すれば良いです。

dot -Tpng interface-tree.dot > interface-tree.png

シーケンスに関連する部分が多いようです。

2013年2月1日金曜日

[Clojure]LazySeqで嵌ったのでメモ

ClojureでCSVファイルを読み込んでいたら OutOfMemoryError に出会ってしまったので 戒めの意味をこめてメモ。


先頭2行がヘッダで残りの行がデータ、というExcelチックなファイルを読み込んでみます。

まず、読み込む対象となる大きなCVSファイルを作成します。

(require '[clojure.data.csv :as csv])

;; ファイル名
(def LARGE-CSV-FILE "large-file.csv")

;; ファイル作成
(with-open [w (clojure.java.io/writer LARGE-CSV-FILE)]
  (.write w "ID1, ID2, ID3, ID4\n")
  (.write w "a, b, c, d\n")
  (doseq [x (range 5000000)]
    (.write w (format "%s, %s, %s, %s\n" (rand) (rand) (rand) (rand)))))

CSVファイルを読み込んで指定した関数を実行する補助関数を定義します。
clojure.data.csv/read-csv はCSVファイルの内容を表すLazySeqを返すので、 指定された関数に渡される際にはまだ実際の読み込み処理は行われていません。

;; CSVを表すLazySeqを引数として関数を実行する関数
(defn call-with-large-csv [f]
  (with-open [r (clojure.java.io/reader LARGE-CSV-FILE)]
    (f (csv/read-csv r))))

CSVファイルを表すLazySeqから、先頭行を取得してみます。

(call-with-large-csv
  (fn [csv] (first csv)))
;; => ["ID1" " ID2" " ID3" " ID4"]

この処理は即座に終了します。 LazySeqのおかげで、実際に読み込まれたのは500万行あるCSVファイルのうち1行だけ(のはず)です。

次に、 ヘッダを表す先頭の2行を取得してみます。

(call-with-large-csv
  (fn [csv] (take 2 csv)))
;; => IOException Stream closed  java.io.BufferedReader.ensureOpen

この処理は正常に終了しません。 「ストリームが既にクローズされています」という例外が発生してしまいます。

これは、take が返す値がLazySeqであるために起こります。
補助関数(call-with-large-csv)の外側でLazySeqの要素が必要(replで表示したい)になると、 その時になってから実際の読み込み処理が行われますが、このときにはもう(with-openによって)入力ストリームはクローズしています。

この処理を正常に行うには、実際の読み込み処理をストリームがオープンしている間に行わなければなりません。 読み込み処理を行いたいタイミングで、doallマクロで囲んだり、ベクタに変換したりしましょう。

(call-with-large-csv
  (fn [csv] (doall (take 2 csv))))
;; => (["ID1" " ID2" " ID3" " ID4"] ["a" " b" " c" " d"])

(call-with-large-csv
  (fn [csv] (vec (take 2 csv))))
;; => [["ID1" " ID2" " ID3" " ID4"] ["a" " b" " c" " d"]]

次に、ファイルの最終行を取得してみます。

(call-with-large-csv
  (fn [csv] (last csv)))
;; => ["0.9335203181407478" " 0.19775692522243427" " 0.21165896344265756" " 0.9433273177661132"]

多少時間はかかりますが、正常に終了します。

同様に、doseqで各行に対して処理を行うふりをしても、処理は正常に終了します。

(call-with-large-csv
  (fn [csv]
    (doseq [x csv] nil)))
;; => nil

doseqで処理を行った後で、先頭行を取得してみます。

(call-with-large-csv
  (fn [csv]
    (doseq [x csv] nil)
    (first csv)))
;; => java.lang.OutOfMemoryError: Java heap space

この処理はメモリが足りないと言われて異常終了します。
処理の最後にLazySeqの先頭要素が必要なので、CSVファイルの内容を保持しておくために doseq中にGCでメモリを回収できないからだと思われます。

以下のようにdoseqよりも前に先頭行を取得しておけば、CSVファイル(LazySeq)の内容すべてを 保持する必要はないため、メモリが足りなくなることなく正常に終了します。

(call-with-large-csv
  (fn [csv]
    (let [header (first csv)]
      (doseq [x csv] nil)
      header)))
;; => ["ID1" " ID2" " ID3" " ID4"]

次に、先頭行だけでなく、先頭の2行(ヘッダ)を取得してみます。

;; 先頭2行のヘッダを取得する関数
(defn get-headers [csv]
  (take 2 csv))

(call-with-large-csv
  (fn [csv]
    (let [headers (get-headers csv)]
      (doseq [x csv] nil)
      headers)))
;; => java.lang.OutOfMemoryError: Java heap space

この処理は異常終了します。 原因は get-headers が返す値がLazySeqであり、doseqの後までCSVの内容を保持しておく必要があるためかと思います。

私はここで悩みました。直接take関数などを書いていればすぐに気づいたかもしれませんが、 「ヘッダを取得」という関数を作成したつもりでいたために返値がLazySeqであるということを忘れていました。

この場合も、処理を正常に終了させるためには、あらかじめdoallなどでLazySeqの要素を計算し、 巨大なデータを保持しなくても良いようにします。

(call-with-large-csv
  (fn [csv]
    (let [headers (doall (get-headers csv))]
      (doseq [x csv] nil)
      headers)))
;; => (["ID1" " ID2" " ID3" " ID4"] ["a" " b" " c" " d"])

ファイルに限らず、LazySeqで巨大なデータを扱うときは、処理の途中でGCできるかどうか気を付けようと思います。

2013年1月25日金曜日

Clojure(java)で日付を扱う

Clojure(Java)で日付を扱うには、java.util.Date、java.util.Calendar、java.text.SimpeDateFormatなどを使えば良さそうです。

;;; リテラル
#inst "2013-01-24"
;; => #inst "2013-01-24T00:00:00.000-00:00"
#inst "2013-01-24T00:00:00Z"
;; => #inst "2013-01-24T00:00:00.000-00:00"
(type #inst "2013-01-24")
;; => java.util.Date


;;; 文字列 => java.util.Date
(java.util.Date. "2013/01/24")
;; => #inst "2013-01-23T15:00:00.000-00:00"
(java.util.Date. "2013/01/24 01:02:03")
;; => #inst "2013-01-23T16:02:03.000-00:00"

(def date-format
  (doto (java.text.SimpleDateFormat. "yyyy/MM/dd hh:mm:ss")
    (.setTimeZone (java.util.TimeZone/getTimeZone "UTC"))))

(.parse date-format "2013/01/24 01:02:03")
;; => #inst "2013-01-24T01:02:03.000-00:00"

(-> (doto (java.text.SimpleDateFormat. "yyyy/MM/dd hh:mm:ss")
      (.setTimeZone (java.util.TimeZone/getTimeZone "JST")))
    (.parse "2013/01/24 01:02:03"))
;; => #inst "2013-01-23T16:02:03.000-00:00"


;;; java.util.Date => 文字列
(.format date-format (.parse date-format "2013/01/24 01:02:03"))
;; => "2013/01/24 01:02:03"

;;; UNIX time (1970/01/01 00:00:00 をエポックとする通算秒)
(defn date->unixtime [d]
  (long (/ (.getTime d) 1000)))

(date->unixtime (.parse date-format "1970/01/01 00:00:01"))
;; => 1
(date->unixtime (.parse date-format "1970/01/02 00:00:00"))
;; => 86400

;;; java.util.Date => java.util.Calendar
(defn date->calendar [d]
  (doto (java.util.Calendar/getInstance)
    (.setTime d)))

(date->calendar (.parse date-format "2013/01/24 00:00:00"))
;; => #inst "2013-01-24T09:00:00.000+09:00"
(type (date->calendar (.parse date-format "2013/01/24 00:00:00")))
;; => java.util.GregorianCalendar

;;; 1日毎の日にちのシーケンス
(defn day-seq [day-from]
  (let [c (date->calendar day-from)]
    (lazy-seq
     (cons day-from
           (repeatedly #(do (.add c java.util.Calendar/DAY_OF_YEAR 1)
                            (.getTime c)))))))

(take 3 (day-seq (.parse date-format "2013/01/24 01:02:03")))
;; => (#inst "2013-01-24T01:02:03.000-00:00"
;;     #inst "2013-01-25T01:02:03.000-00:00"
;;     #inst "2013-01-26T01:02:03.000-00:00")

その他、Clojure用のライブラリとして clj-time というのがあるようです。

2013年1月22日火曜日

[Clojure]mapの操作

Clojureでmapを作成・操作する関数のメモ。多くの関数は他のシーケンス・コレクションにも適用可能。

(def a-map {:k1 :v1 :k2 :v2})

;;; get関数でアクセスできる
(get a-map :k1)
;; => :v1
(get a-map :not-found)
;; => nil
(get a-map :not-found :default)
;; => :default

;;; キーワードはマップへのアクセサとして扱える
(:k1 a-map)
;; => :v1
(:not-found a-map)
;; => nil
(:not-found a-map :default)
;; => :default

;;; マップ自体をキーを引数に取る関数として扱える
(a-map :k2)
;; => :v2
(a-map :not-found)
;; => nil
(a-map :not-found :default)
;; => :default

;;; find関数でアクセスするとキーと値のペア(map entry)が取得できる
(find a-map :k2)
;; => [:k2 :v2]
(find a-map :not-found)
;; => nil

;;; すべてのキーを取得する
(map key {:k1 :v1 :k2 :v2})
;; => (:k1 :k2)
(keys {:k1 :v1 :k2 :v2})
;; => (:k1 :k2)

;;; すべての値を取得する
(map val {:k1 :v1 :k2 :v2})
;; => (:v1 :v2)
(vals {:k1 :v1 :k2 :v2})
;; => (:v1 :v2)

;;; 複数のキーに対応する値を取得する
(select-keys {:k1 :v1 :k2 :v2 :k3 :v3} [:k1 :k3])
;; => {:k3 :v3, :k1 :v1}
((juxt :k1 :k3) {:k1 :v1 :k2 :v2 :k3 :v3})
;; => [:v1 :v3]

;;; 一部を抜き出す (sorted-map)
(subseq (sorted-map 1 2 3 4 5 6) <= 3)
;; => ([1 2] [3 4])

;;; 要素を追加する
(assoc a-map :k3 :v3)
;; => {:k3 :v3, :k1 :v1, :k2 :v2}
(assoc a-map :k3 :v3 :k4 :v4)
;; => {:k4 :v4, :k3 :v3, :k1 :v1, :k2 :v2}
(conj a-map [:k3 :v3])
;; => {:k3 :v3, :k1 :v1, :k2 :v2}
(conj a-map [:k3 :v3] [:k4 :v4])
;; => {:k4 :v4, :k3 :v3, :k1 :v1, :k2 :v2}
(merge {:k1 :v1} {:k2 :v2})
;; => {:k2 :v2, :k1 :v1}
(merge {:k1 :v1} [:k2 :v2])
;; => {:k2 :v2, :k1 :v1}
(merge-with concat {:k1 [1 2]} {:k1 [3 4]})
;; => {:k1 (1 2 3 4)}
(into {:k1 :v1} {:k2 :v2})
;; => {:k1 :v1, :k2 :v2}
(into {:k1 :v1} [[:k2 :v2]])
;; => {:k1 :v1, :k2 :v2}

;;; 指定したキーを削除
(dissoc {:k1 :v1 :k2 :v2} :k1)
;; => {:k2 :v2}
(dissoc {:k1 :v1 :k2 :v2} :k1 :k2)
;; => {}

;;; 空かどうかチェックする
(if (empty? {}) :a :b)
;; => :a
(if (empty? {:key :val}) :a :b)
;; => :b
(if (not-empty {}) :a :b)
;; => :b
(if (not-empty {:key :val}) :a :b)
;; => :a
(if (seq {}) :a :b)
;; => :b
(if (seq {:key :val}) :a :b)
;; => :a

;;; 指定したキーが存在するかチェックする
(contains? {} :key)
;; => false
(contains? {:key :val} :key)
;; => true


;;; alist風のベクタからマップを作成する
(def alist [[1 2] [3 4]])

(into {} alist)
;; => {1 2, 3 4}

(apply merge {} alist)
;; => {3 4, 1 2}

(apply conj {} alist)
;; => {3 4, 1 2}

;;; plist風のベクタからマップを作成する
(def plist [1 2 3 4])

(apply assoc {} plist)
;; => {3 4, 1 2}

(apply sorted-map plist)
;; => {1 2, 3 4}

;;; keyのベクタとvalsのベクタからマップを作成する
(def ks [1 3])
(def vs [2 4])

(zipmap ks vs)
;; => {3 4, 1 2}

(apply assoc {} (interleave ks vs))
;; => {3 4, 1 2}

;;; ネストしたマップへアクセスする
(get-in {:a {:b 2}} [:a :b])
;; => 2

(get-in {} [:a :b] :default)
;; => :default

(assoc-in {} [:a :b] 2)
;; => {:a {:b 2}}

(update-in {:a {:b 2}} [:a :b] inc)
;; => {:a {:b 3}}


;;; その他
(frequencies [:a :b :a :a :c]) ; 出現回数
;; => {:a 3, :b 1, :c 1}

(group-by even? [1 2 3 4]) ; 関数の適用結果によるグループ分け
;; => {false [1 3], true [2 4]}