2011年1月アーカイブ

38行でGoogle ClosureのCLラッパーを作る

| 【6分で読めるよ!】 | トラックバック(0)

 Javaわっかんねーwwwって言ってたらこの開発タームからUIチームに配属されました。最近仕事ではJavaScriptとかJSPを書いてます。

「仕事は楽しいですか」
「んー、、、楽し…くなくはないです」
「JavaScriptは嫌いな言語じゃないですよね」
「まあ、、好きってほどでもないですが」

 まあ、Perl5とJavaScriptは嫌いじゃないですが、やっぱりLispに敵うものはありません。そう言うと、

「それなら、Lispで書いてJavaScriptに変換すればいいんじゃないですか

 とかCTOに煽られたので試してみました。

Parenscript概要

 Common LispにはParenscriptというライブラリがあります。これはCLでJavaScriptコードを生成するライブラリです。Parenscriptを使えばJavaScriptをS式で記述できます。

 Parenscriptは、よくも悪くも、薄いトランスレータです。JavaScriptのコードとParenscriptの記述は一対一で対応しています。

 たとえば、以下のJavaScriptコードは、

goog.array.forEach([1, 2, 3], function (x) {
  return x % 2;
});

 以下のようなParenscriptで書けます。

(ps ((@ goog array for-each) '(1 2 3) (lambda (x) (oddp x))))

 関数名の(@ goog array for-each)はJSのシンボルを表しています。

 また、コンストラクタはps内でnewを使います。アッパーキャメルケースにするにはハイフンを前置する必要があります。

(ps ((new (@ goog ui -tab-bar)) 'x))
;=> "new goog.ui.TabBar('x');"

 ps*はフォームを受け取ってpsで評価する関数です。Parenscriptの中でCLを呼びだそうと思ったらこちらを使います。

冗長すぎる

 ただ、これは生のJavaScriptに比べても随分と冗長です。特にシンボルが。

 なんで以下のように書けないんでしょう?

(goog.array:for-each '(1 2 3) (lambda (x) (oddp x)))

 こう書ければClosure Libraryと同じように記述できるのに!

 仕方ない。ラッパー書くか。

Google Closureのラッパーを作る

 こういった単純なラッパーのように、呼ばれる関数名によって処理をわけたいとき、特にその関数名が無数にあるときは手作業ではやってはいられません。

 こういうときどうするか。AUTOLOADです。こんなこともあろうかと先に書いておきました。

 まずは以下のようにundefined-function-hookを定義しておきます。

(defpackage goog.array
  (:use :cl :parenscript :cl-ppcre))
(in-package :goog.array)

(setf parenscript:*indent-num-spaces* 2)

(defvar undefined-function-hook
    (lambda (condition hook)
      (declare (ignore hook))
      (let ((symb (cell-error-name condition)))
        (lambda (&rest args)
          (ps:ps* `((ps:@
                     ,@(mapcar #'intern (cl-ppcre:split "\\." (package-name (symbol-package symb)))) ,symb )
                    ,@(mapcar
                       (lambda (a)
                         (if (and (listp a) (eq (car a) 'lambda))
                             a
                             (list 'quote a)))
                       args)))))))

 そのあとにgoog.array::for-eachを呼び出すと、

(goog.array::for-each '(1 2 3) '(lambda (x) (oddp x)))
;=>
"goog.array.forEach([1, 2, 3], function (x) {
  return x % 2;
});"

 といった具合に等価なJSコードを吐いてくれます。エクスポートされたシンボルではないのでコロンが2つ必要なことに注意です。

 また、コンストラクタは以下のような関数newを定義しておけば、

(defun new (code)
  (concatenate 'string "new " code))

 これも同じように書けます。

(new (goog.ui::-tab-bar 'x))
;=> "new goog.ui.TabBar('x');"

 JSDocとかはParenscriptで吐けそうにないので自前でformatとか用意すればいいかなーって感じです。もしくはdeclareの情報を取ってきて型情報をJSDocに吐くとかできたらクールかもね。

ラッパー全コード

 全部で38行です。*goog-packages*にパッケージ名を追加すれば他のパッケージにも対応できます。

(require 'cl-ppcre)
(require 'parenscript)

(in-package :cl-user)

(setf parenscript:*indent-num-spaces* 2)

(setf *debugger-hook*
      (lambda (condition hook)
        (when (typep condition 'undefined-function)
          (let* ((pkg (package-name (symbol-package (cell-error-name condition))))
                 (local-hook (ignore-errors (symbol-value (intern "UNDEFINED-FUNCTION-HOOK" pkg)))))
            (when (and pkg (functionp local-hook))
              (setf (symbol-function (cell-error-name condition))
                    (funcall local-hook condition hook))
              (invoke-restart (find-restart 'continue condition)))))))

(defparameter *goog-packages* '(goog goog.ui goog.array))
(defvar *undefined-function-hook*
    (lambda (condition hook)
      (declare (ignore hook))
      (let ((symb (cell-error-name condition)))
        (lambda (&rest args)
          (ps:ps* `((ps:@
                     ,@(mapcar #'intern (cl-ppcre:split "\\." (package-name (symbol-package symb)))) ,symb)
                    ,@(mapcar
                       (lambda (a)
                         (if (and (listp a) (eq (car a) 'lambda))
                             a
                             (list 'quote a)))
                       args)))))))

#.`(progn ,@(loop for pkg in *goog-packages*
                  collect `(defpackage ,pkg (:use :cl :parenscript :cl-ppcre))))

#.`(progn ,@(loop for pkg in *goog-packages* collect
                  `(defvar ,(intern "UNDEFINED-FUNCTION-HOOK" pkg)
                       *undefined-function-hook*)))

謹賀新年

| 【0分で読めるよ!】 | トラックバック(0)

happy-new-year-2011.png

このアーカイブについて

このページには、2011年1月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2010年12月です。

次のアーカイブは2011年2月です。

Name:深町英太郎
Age:歳 (1987〜)
Living:京都府
Hatena Id:id:nitro_idiot
Facebook:eitarow.fukamachi
mixi:ID:6756132
Twitter:nitro_idiot
GitHub:fukamachi
LinkedIn:eitarowfukamachi

最近のコメント

Techonrati

Technorati search

» リンクしているブログ

Powered by Movable Type 4.23-ja