/home/matstani/weblog

programming log.

[Clojure] iTextでPDFファイル作成(ウェブアプリケーション)

CompojureベースのClojureウェブアプリケーションで、iTextを利用したPDFファイル出力を行う手順をメモします。(ファイルに保存する例はこちら。)
例として、SQLiteデータベースから従業員データを読み取り、PDFファイルとして出力することを考えてみます。

テスト用プロジェクト作成

1
2
$ lein new compojure try-itext-compojure
$ cd try-itext-compojure

project.clj:dependencies[com.itextpdf/itextpdf "5.4.4"] [com.itextpdf/itext-asian "5.2.0"] [org.xerial/sqlite-jdbc "3.7.2"] [korma "0.3.0-RC5"]を追記します。

1
$ lein deps

データベース準備

1
2
3
4
5
$ mkdir db
$ sqlite3 db/try-itext.sqlite3 "CREATE TABLE employees (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"
$ sqlite3 db/try-itext.sqlite3 "INSERT INTO employees (name) VALUES ('名無 一郎');"
$ sqlite3 db/try-itext.sqlite3 "INSERT INTO employees (name) VALUES ('名無 二郎');"
$ sqlite3 db/try-itext.sqlite3 "INSERT INTO employees (name) VALUES ('名無 三郎');"

サンプルコード

handler.clj

起動

1
$ lein ring server-headless

ブラウザでlocalhost:3000にアクセスするとPDFファイルが表示されます。

[Clojure] iTextでPDFファイル作成

ClojureでPDFファイルを作成するためのライブラリとしてclj-pdfがありますが、そのままでは日本語フォントに対応していないようなので、直接iTextを使う手順を紹介します。

テスト用プロジェクト作成

1
2
$ lein new app try-itext
$ cd try-itext

project.clj:dependencies[com.itextpdf/itextpdf "5.4.4"] [com.itextpdf/itext-asian "5.2.0"]を追記します。

1
$ lein deps

サンプルコード

core.clj

プログラム実行

1
$ lein run

実行すると、test.pdfが作成されます。

[Clojure] lein-typed

lein-typedを利用すると、core.typedライブラリを利用したClojureプログラムの静的型チェックをLeiningenタスクとして実行できます。

インストール

全てのプロジェクトで利用する場合

~/.lein/profiles.clj:user :plugins[lein-typed "0.3.0"]を追記します。(ファイルが存在しない場合は新規作成)
例)

1
{:user {:plugins [[lein-typed "0.3.0"]]}}

特定のプロジェクトで利用する場合

project.clj:plugins[lein-typed "0.3.0"]を追記します。

型チェックの実行

project.cljにチェック対象となるnamespaceを記述します。
例)

1
2
3
4
5
6
7
8
9
10
(defproject try-typed "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
  :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/core.typed "0.2.3"]]
  :plugins [[lein-typed "0.3.0"]] 
  ; 対象namespaceを指定
  :core.typed {:check [try-typed.core]})

静的型チェックを実行します。

1
2
3
4
5
6
7
8
9
10
$ lein typed check
Initializing core.typed ...
"Elapsed time: 11563.779405 msecs"
core.typed initialized.
Start collecting try-typed.core
Finished collecting try-typed.core
Collected 1 namespaces in 11658.922614 msecs
Start checking try-typed.core
Checked try-typed.core in 423.743596 msecs
Checked 1 namespaces (approx. 33 lines) in 12087.276047 msecs

checkの替わりにcoverageを指定すると、型指定annotationの網羅率を表示します。

1
2
3
4
5
6
7
8
9
10
lein typed coverage
Initializing core.typed ...
"Elapsed time: 3286.597969 msecs"
core.typed initialized.
Start collecting try-typed.core
Finished collecting try-typed.core
Collected 1 namespaces in 3383.256821 msecs
Checked 0 namespaces (approx. 0 lines) in 3385.801947 msecs
Found 4 annotated vars out of 4 vars
100% var annotation coverage

[Clojure] core.typedで静的型チェック

Clojureは動的型付けの言語ですが、core.typedライブラリを利用すると、静的型チェックを行うことができます。
バージョン0.2.0リリースで、Production Readyとアナウンスされました。
単純な例を試してみたのでメモします。

テスト用プロジェクト作成

1
2
$ lein new try-typed
$ cd try-typed

project.clj[org.clojure/core.typed "0.2.3"]を追記します。

1
$ lein deps

静的型チェックの実行

core.typedを利用した静的型チェックは、プログラムのコンパイルとは別に、明示的に実行します。
REPL内でclojure.core.typed/check-ns関数を実行することにより、指定したnamespaceをチェックします。

1. REPL起動

1
$ lein repl

2. 静的型チェック実行

先ほど作成したテスト用プロジェクトに対し、型チェックを行ってみます。

1
2
user=> (use 'clojure.core.typed)
user=> (check-ns 'try-typed.core)

上記を実行すると、以下のようにエラーになります。

1
2
3
Type Error (try-typed.core:3:1) Found untyped var definition: try-typed.core/foo
Hint: Add the annotation for try-typed.core/foo via check-ns or cf
in: (def foo (fn* ([x] (clojure.core/println x Hello, World!))))

core.typedによる静的型チェックでは、全てのvarが明示的に型付けされている必要があります。
上記では、try-typed.core/foo関数に型付けがされていないため、エラーになっています。

varに型付けを行う

clojure.core.typed/annで、varに型付けを行うことができます。
上記でエラーになったtry-typed.core/fooに型付けしてみます。
core.clj

上記の例では、try-typed.core/fooに対し「String型の引数をとりnilを返す」という型情報を付与しています。
この状態で型チェックを行うと、:okとなります。

1
2
3
4
5
6
7
8
user=> (check-ns 'try-typed.core)
Start collecting try-typed.core
Finished collecting try-typed.core
Collected 1 namespaces in 16.039067 msecs
Start checking try-typed.core
Checked try-typed.core in 65.01378 msecs
Checked 1 namespaces (approx. 8 lines) in 84.795137 msecs
:ok

型不一致エラー

clojure.core.typed/annで指定した型付けと整合しない呼び出しがあった場合、エラーとして検出されます。
例) core2.clj

1
2
3
4
5
6
7
8
9
10
11
12
user=> (check-ns 'try-typed.core)
Start collecting try-typed.core
Finished collecting try-typed.core
Collected 1 namespaces in 15.624342 msecs
Start checking try-typed.core
Checked try-typed.core in 33.318364 msecs
Type Error (try-typed.core:13:3) Type mismatch:

Expected:       String

Actual:         (Value 1.5)
in: (try-typed.core/foo 1.5)

型情報の参照

clojure.core.typed/cfで型情報を参照できます。

1
2
user=> (cf try-typed.core/foo)
(Fn [String -> nil])

標準関数についても、ほとんどがライブラリ内で型付けされています。

1
2
user=> (cf inc)
(Fn [AnyInteger -> AnyInteger] [Number -> Number])

自作関数と、標準関数の間で型の不整合があった場合には、エラーとして検出されます。
例) core3.clj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
user=> (check-ns 'try-typed.core)
Start collecting try-typed.core
Finished collecting try-typed.core
Collected 1 namespaces in 22.306404 msecs
Start checking try-typed.core
Checked try-typed.core in 36.899735 msecs
Type Error (try-typed.core:8:3) Static method clojure.lang.Numbers/inc could not be applied to arguments:


Domains:
        AnyInteger
        Number
                
Arguments:
        String
        
Ranges:
        AnyInteger
        Number
                
with expected type:
        AnyInteger
        
in: (clojure.lang.Numbers/inc s)

関数内で正しく型変換していると、チェックは:okになります。
例) core4.clj

1
2
3
4
5
6
7
8
user=> (check-ns 'try-typed.core)
Start collecting try-typed.core
Finished collecting try-typed.core
Collected 1 namespaces in 36.006706 msecs
Start checking try-typed.core
Checked try-typed.core in 38.304219 msecs
Checked 1 namespaces (approx. 9 lines) in 78.396396 msecs
:ok

型付けの例

引数が複数

1
2
3
(ann my-add [Number, Number -> Number])
(defn my-add [n1 n2]
  (+ n1 n2))

可変長引数

1
2
3
(ann my-add [Number * -> Number])
(defn my-add [& n]
  (apply + n))

シーケンス

1
2
3
(ann my-add [(Seqable Number) -> Number])
(defn my-add [n]
  (apply + n))

Set

1
2
3
(ann my-filter [(Set AnyInteger) -> (Seqable AnyInteger)])
(defn my-filter [n]
  (filter odd? n))

Map

1
2
3
(ann get-count [(Map clojure.lang.Symbol Number) -> (U nil Number)])
(defn get-count [m]
  (m :count))

無名関数

高階関数に無名関数を渡す場合、型付けが必要になる場合があるようです。 clojure.core.typed/fn>による型付け

1
2
3
4
5
6
7
(ann inc-seq [Number, (Seqable Number) -> (Seqable Number)])
(defn inc-seq
  [i s]
    (map
       ; clojure.core.typed/fn> は無名関数のパラメータに型情報を付加できる
       (fn> [e :- Number] (+ i e))
       s))

clojure.core.typed/ann-formによる型付け

1
2
3
4
5
6
7
(ann inc-seq [Number, (Seqable Number) -> (Seqable Number)])
(defn inc-seq
  [i s]
    (map
       ; clojure.core.typed/ann-form> は任意のフォームに型情報を付加できる
       (ann-form #(+ i %) [Number -> Number])
       s))

その他

Clojureには標準で型ヒント(type hinting)と呼ばれる機能がありますが、Javaを呼び出す際のリフレクションを防ぐためのもので、core.typedのような、静的型チェックの機能ではありません。

[Clojure] ちょっと難しいシーケンス処理

Sequence Abstractions in Clojure

上記のリンクはClojureのシーケンスに関する記事なのですが、”Difficult Sequences”のところが面白かったのでメモします。

Clojureでは、mapfilterを始めとした豊富なシーケンス用関数がありますが、例えば「1つ前の要素を参照する」処理はどう書くのでしょうか。
手続き型言語では、以下のように記述できます。

1
2
3
4
v = [1 2 3 4 5];
for (i=1; i<v.length; i++)
    print v[i] + v[i-1];
-> 3 5 7 9

この「1つ前の要素を参照する」処理をシーケンスで実現するために、まず「1つずらしたシーケンス」を考えてみます。

1
2
  [1 2 3 4 5]
[1 2 3 4 5]

すると、上下が重なっている部分が、「1つ前の要素」と「現在の要素」の組み合わせになっています。
Clojureのmap関数では、復数のシーケンスを同時に扱うことができます。

1
2
(map + [1 3] [2 4])
-> (3 7)

渡したシーケンスの最初の要素同士、2番目の要素同士、がそれぞれ”+”されています。
「元のシーケンス」、「1つずらしたシーケンス」、を用意してmap関数に渡してやれば、目的の処理が実現できそうです。
「1つずらしたシーケンス」を得るには、rest関数が利用できます。

1
2
3
(def v [1 2 3 4 5])
(rest v)
-> (2 3 4 5)

rest関数が返すのは、シーケンスの先頭以外を含むシーケンスです。
これらを組み合わせると目的の処理を実現できます。

1
2
(map + v (rest v))
-> (3 5 7 9)

ここでは

1
2
v        -> (1 2 3 4 5)
(rest v) -> (2 3 4 5)

上記2つのシーケンスの重なっている要素同士の足し算”+”が行われ、目的が実現できています。
map関数に長さの異なるシーケンスを渡された場合、長いシーケンスの余り部分は無視されます。

clojure/core.asyncでGo

clojure/core.asyncライブラリを利用すると、Go言語のGoroutineと同様に、チャネルを介した平行プログラミングを行うことができます。

試しに、Go言語チュートリアルに掲載されている素数の篩(ふるい)をClojureで実装してみました。

project.clj

core.clj

Go言語版をほぼそのままの形で実装できています。
他言語の構文をライブラリとして提供できるのはLispならではです。

ちなみに、ClojureScript(Clojure-JavaScriptコンパイラ)でも利用可能とのことです。

lein-tryでClojureのライブラリを手軽にお試し

Leiningenのプラグインであるlein-tryを利用すると、わざわざテスト用のプロジェクトを作らなくても、手軽にライブラリをREPL上で試すことができます。

lein-tryのインストール

~/.lein/profiles.clj:user :plugins[lein-try "0.3.0"]を追記します。(ファイルが存在しない場合は新規作成)
例)

1
{:user {:plugins [[lein-try "0.3.0"]]}}

lein-tryでライブラリを試す

clj-timeを試してみます。

1
2
3
4
5
$ lein try clj-time
user=> (require '[clj-time.core :refer [date-time]])
nil
user=> (date-time 2013 07 30 18 00 00)
#<DateTime 2013-07-30T18:00:00.000Z>

ちなみに、Leiningenのバージョンが2.1.3より古い場合エラーになることがあります。Leiningenを最新の安定版にアップデートするには、以下のコマンドを実行します。

1
$ lein upgrade

参考にした記事
Try Clojure libraries with ease using lein-try

[Clojure] インデックス付きシーケンス

Clojure1.2以前の、clojure.contrib.seq/indexedの代替として、インデックス付きシーケンスがほしいときは、以下のコードが使えます。

1
2
> (map-indexed vector [:a :b :c :d :e])
;([0 :a] [1 :b] [2 :c] [3 :d] [4 :e])

例)doseqでループ処理。

1
2
3
4
5
6
> (doseq [[idx item] (map-indexed vector [:a :b :c :d :e])]
        (println (str idx ":" item)))
;0:a
;1:b
;2:c
;3:d

[PHP]memory_get_usageのreal_usageオプションについて

PHPで割り当てメモリ量を取得するには、memory_get_usage関数を利用できます。

この関数には、パラメータとして$real_usageを指定できるようになっており、

これを TRUE に設定すると、システムが割り当てた実際のメモリの大きさを取得します。 省略したり FALSE を設定したりすると、 emalloc() が使用するメモリのみを報告します。

とあるのですが、「システムが割り当てた実際のメモリの大きさ」と、「emalloc()が使用するメモリ」の違いがよくわかりません。

検索してみたところ、以下を見つけました。

http://stackoverflow.com/questions/2290611/tracking-memory-usage-in-php

回答によると、PHPのメモリマネージャは、メモリを確保する際、アプリケーションが必要とするメモリをその都度mallocするのではなく、より大きな単位で確保しておいて内部で管理しているそうです。(デフォルトで256KB単位。環境変数ZEND_MM_SEG_SIZEで設定)

ということで、割り当てメモリ量として以下2種類があることになります。

  1. PHPのメモリマネージャが確保しているメモリ量($real_usage=true)
  2. アプリケーションが実際に使用しているメモリ量($real_usage=false)

php.ini等で指定するmemory_limitで制限しているのは1.になります。
環境変数 USE_ZEND_ALLOC=0 を設定すると、上記の仕組みが無効になり、必要量をその都度mallocする動作になるとのことです。

ちなみに、PHPの割り当てメモリ量を調べる関数としては、memory_get_usageの他に、memory_get_peak_usageがあり、こちらは呼び出し時点までに割り当てられた最大メモリ量を取得できます。