Common LispにThreaded Macroを実装する

PythonからいいところをひとつもってこれたのでClojureからももってくる。
Clojureでは数珠つなぎで処理する場所はわかりやすくThreaded Macroを使って書くことが多い。
これはわかりやすいのでCommon Lispでも実装しておく。

(defmacro -> (x &optional form &rest more)
  (cond ((not (null more))
         `(-> (-> ,x ,form) ,@more))
        ((not (null form))
         (if (listp form)
             `(,(first form) ,x ,@(rest form))
           (list form x)))
        (t x)))

(defmacro ->> (x form &rest more)
  (cond ((not (null more))
         `(->> (->> ,x ,form) ,@more))
        (t (if (listp form)
               `(,(first form) ,@(rest form) ,x)
             (list form x)))))

適当に例をあげる

(-> 25 sqrt (* 2) (- 3))

結果は

7.0

Clackなどのmiddlewareを書く際にもThreaded Macroの方がわかりやすいんじゃないかなと思う。
(ringのMiddlewareはThreaded Macroを使って書く事が多い)

これでまたひとつClojureにあるライブラリを移植しやすくなった。

Common LispでPythonのgeneratorを実装する

結局Lisp書かないとダメだということで本格的にPythonから移行しようと思ってます。
まず、今使ってるツールなどをLispに置き換えようと思ったらまあgeneratorが無くてめんどいことに。
というわけでgeneratorを実装しておく。
この手はみんな実装してるので珍しくもなんともない。
定番のcl-contを使う。

(require 'cl-cont)

(defun mkstr (&rest args)
  (with-output-to-string (s)
    (dolist (a args) (princ a s))))

(defun symb (&rest args)
  (values (intern (apply #'mkstr args))))

(defun flatten (x)
  (labels ((rec (x acc)
             (cond ((null x) acc)
                   ((atom x) (cons x acc))
                   (t (rec
                       (car x)
                       (rec (cdr x) acc))))))
    (rec x nil)))

(defun g!-symbol-p (s)
  (if (symbolp s)
      (let ((str (symbol-name s)))
        (string= str "#" :start1 (1- (length str))))))

(defun o!-symbol-p (s)
  (if (symbolp s)
      (let ((str (symbol-name s)))
        (string= str "%" :start1 (1- (length str))))))

(defun o!-symbol-to-g!-symbol (s)
  (let ((str (symbol-name s)))
    (symb (subseq str 0 (1- (length str)))
          "#")))

(defmacro defmacro/g! (name args &body body)
  (let ((symbs (remove-duplicates
                (remove-if-not #'g!-symbol-p
                               (flatten body)))))
    `(defmacro ,name ,args
       (let ,(mapcar
              (lambda (s)
                `(,s (gensym ,(subseq
                               (symbol-name s)
                               2))))
              symbs)
         ,@body))))

(defmacro defmacro* (name args &body body)
  (let* ((os (remove-if-not #'o!-symbol-p args))
         (gs (mapcar #'o!-symbol-to-g!-symbol os)))
    `(defmacro/g! ,name ,args
       `(let ,(mapcar #'list (list ,@gs) (list ,@os))
          ,(progn ,@body)))))

(defmacro* make-generator (&body body)
  `(let (,cont#)
     (cl-cont:with-call/cc
       (labels ((,(intern "YIELD") (&rest values)
                  (cl-cont:let/cc k
                    (setf ,cont# k)
                    (apply #'values values)
                    )))
         (,(intern "YIELD") (lambda () (cl-cont:call/cc ,cont#)))
         ,@body
         (loop (,(intern "YIELD") :done))))))

(defmacro* defgenerator (name args &body body)
  `(defun ,name ,args
     (make-generator ,@body)))

(declaim (inline next))
(defun next (g)
  (funcall g))

(defmacro* nif (expr pos zero neg)
  `(let ((,g# ,expr))
       (cond ((plusp ,g#) ,pos)
             ((zerop ,g#) ,zero)
             (t ,neg))))

(defmacro* alambda (args &body body)
  `(labels ((self ,args ,@body))
     #'self))

まあよく使うutilとかもまるっと書いてあるけど。 make-generatorだけみればOK。
generatorが作れたのでPythonのitertoolsも多分実装できます。
itertools.countはこんな感じ。

(defgenerator counter (n)
  (funcall (alambda (n)
             (yield n)
             (self (incf n))) n))


(setf foo (counter 10))
(loop repeat 50 collect (next foo))

結果

(10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
  34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
   58 59)

足りない機能を実装できるのでLispはやはり強力だなあと思う

PySpaアドベントカレンダー 22日目

はじめに

このエントリはPySpaアドベントカレンダーの22日目のエントリです。

PHPとかJavaがクソとか言うエントリではありませんのでご注意願います。

PySpaについて

まあ言うことはありません。ロビーでイリーガルな話やヒドいい話などをする合宿です。

コードとか二の次であってみんなの闇を共有する合宿です。

多分逮捕者がでてもおかしくないです。

最近作ってるもの

せっかくなので最近作ってるもの話をしておきます。

dismage

dismage - MySQL Protocol Server

要は MySQL Protocol を話す Server です。WSGI ライクなインターフェイスを持っています。

isucon 用に作りはじめたのがきっかけです。内部は libdrizzle で作られています。

全て非同期のAPIを使って実装されていますが、毎度のことながら greenlet でその辺を隠蔽してあるので使う人何も意識することはありません。

ほぼ C で書いてあるのでパフォーマンスはそれなりに出ます。

パフォーマンスをあげるためキャッシュなどをする場合、Web アプリケーション側に手が入ってしまったりするのですが(Middleware使えばいいのですが) それらをDB側の方へ追いやることができます。

サンプルコード

from dismage import *
from dismage import patch
patch.patch_all()

def app(cmd, cmd_data, start_result):

    print("cmd:%s data:%s start_response:%s" % (cmd, cmd_data, start_result))
    if cmd == COMMAND_INIT_DB or "COMMIT" in cmd_data:
        return
    columns = [(COLUMN_TYPE_VARCHAR, 10, 'name'), (COLUMN_TYPE_INT24, 8, 'age')]
    start_result(columns=columns)
    return [('john', 30), ('smith', 28)]

listen(port=3307)
run(app)
  • cmd には COMMAND の種別が入ってくる
  • cmd_data には SQL 本文が入ってくる
  • start_result には SELECT 文で返す際の行の定義を設定する

上記の例だと以下のSQLを実行して結果を返しているようなイメージになります。

SELECT name, age FROM x;

SELECT 場合のみ start_result に返す行の定義を設定しますが、その他の場合には設定しなくてよいです。

返り値もNoneで返せばOKです。

非同期対応

socket にパッチがあたってるので pure python な 通信は非同期通信が行われます。

例えば内部キャッシュに無いデータを本来の MySQL へ問い合わせる(中継するイメージ)場合 pymysql などを使えば非同期で MySQL へ問い合わされます。

応用

まだ開発中ですが応用の用途としては以下のようなものがあります。

  • テスト用の MySQL Sever として使う
  • 複数の DB に同時に書き込む
  • SQL を解析して自前で sharding する
  • SQL を解析して一部の SQL を Hive に流す
  • SQL を解析して一部の SQL を Impala に流す
  • INSERT 文を解析して KVS にデータを入れる
  • INSERT 文を解析して fluentd にデータを入れる

ドライバーによっては SQL かどうか判断していないので好きなデータを送信して処理することもできると思います。

(URL を送ってアクセスした結果を返すなどなど)

また WSGI Middleware のようなものも書けるので Middlwareで SQL の方言を直したり、フィルターをかけたりといったこともできます。

お手伝いしてくれる人など募集してます。

(特にlibdrizzleの認証とこに詳しい人)

素数を求める

特に難しいことはない。
とりあえずメモリが許すまで。
100万個ぐらいは数えれる。


fn prime(n: uint) {
    let mut prime: ~[uint] = do vec::build_sized(n) |push| {
        let mut i: uint = 0;
        while i < n {
            push(0);
            i += 1;
        }};
    let mut ptr: uint = 0;
    let mut j: uint = 5;

    prime[ptr] = 2;
    ptr += 1;
    prime[ptr] = 3;
    ptr += 1;
    
    loop {
        let mut i: uint = 1;
        let mut flg: bool = false;
        
        while prime[i] * prime[i] <= j  {

            if (j % prime[i] == 0) {
                flg = true;
                break;
            }
            i += 1;
        }
        if !flg {
            prime[ptr] = j;
            ptr += 1;
        }
        if prime[n - 1] > 0 {
            break;
        }
        j += 2;
    }

    for prime.each |e| {
        io::println(fmt!("%u", *e));
    }
}

fn main() {
    let args = os::args();
    let n = int::from_str(args[1]).get() as uint;
    prime(n);
}