読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

Parenscriptでゲームを作ってみる準備をしてみる

CommonLisp Parenscript JavaScript

Quicklispのインストール

公式の手順に従う。

curl -O http://beta.quicklisp.org/quicklisp.lisp

# 公開鍵が無いってエラーが出た。面倒なので飛ばす
# curl -O http://beta.quicklisp.org/quicklisp.lisp.asc
# gpg --verify quicklisp.lisp.asc

sbcl --load quicklisp.lisp

SBCLのREPLで

(quicklisp-quickstart:install)

でインストール開始。 終わると次の様なメッセージが出た。

  ==== quicklisp installed ====

    To load a system, use: (ql:quickload "system-name")

    To find systems, use: (ql:system-apropos "term")

    To load Quicklisp every time you start Lisp, use: (ql:add-to-init-file)

    For more information, see http://www.quicklisp.org/beta/

そりゃもちろん起動時には勝手にロードしといてほしいな、というわけで

(ql:add-to-init-file)

を評価。

I will append the following lines to #P"/home/phaendal/.sbclrc":

  ;;; The following lines added by ql:add-to-init-file:
  #-quicklisp
  (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                         (user-homedir-pathname))))
    (when (probe-file quicklisp-init)
      (load quicklisp-init)))

Press Enter to continue.

merge-pathnames、user-homedir-pathnameなんてのがあるのか。すぐ使いそうだ。

Parenscriptのインストール

そのままREPL内で(ql:quickload :parenscript)を評価してみる。 ダウンロードが始まったけど、次回からそれを使えるんだろうか? 気になったので(quit)し、今度はsbclのみで起動して、(ql:quickload :parenscript)をまた評価してみると、特に問題無くロードが完了した。大丈夫みたいだ。

試してみる

Parenscript Tutorial

このチュートリアルを見ながら試してみる。

(ql:quickload :parenscript)

(defpackage :test1-ps
  (:use :cl :parenscript))

(in-package :test1-ps)

最初にC-c C-kすると、The name "PARENSCRIPT" does not designate any package.というエラーが出る。sbcl --loadでは読み込めるんだけど。 ql:quickloadがdefpackageより前に評価されてないな、と思ったので、githubでコード探して次の様に書きなおして解決した。

(eval-when (:compile-toplevel)
  (ql:quickload :parenscript))

(defpackage :test1-ps
  (:use :cl :parenscript))

(in-package :test1-ps)

これでC-c C-kが通るようになったんだけど、今度はsbcl --loadでエラーが出る。 1時間ぐらいこれで引っかかったのでとりあえず今日はギブアップ。。 そもそも、何かSlimeでの開発フローを間違えてる気がする。 あんまりベストプラクティス地獄みたいなのにハマり込みたくないんだけどな…

気を取り直して、動くようになったのでparenscriptを使ってみる。

psは内部のlispコードをjavascriptに変換して、文字列として返すみたいだ。

(ps (defvar x)
    (defun double (x) (* x 2))
    (lambda (x) (* x x)))

SlimeでC-c C-pすると結果が表示される。

"var x;
function double(x) {
    return x * 2;
};
function (x) {
    return x * x;
};"

気になったんだけど、psの外部で定義された関数はどうなるんだろう?

(defun double (x) (* x 2))

(ps (defvar x)
    (double x))

defunをC-c C-cしてpsでC-c C-p

"var x;
double(x);"

自動的に定義を拾ってきてくれるわけでは無いみたいだ。残念。

psの外部で関数やマクロを十分にテストして、(ps (app 'init) (app 'start))みたいにやればいいかなと思ってたけど、ps内部にコードを書かないといけない。どうやって開発サイクルを加速させようかな?

少しParenscript書いてみる

(print 
 (ps 
   ((lambda (global)
      (defmacro inner-value (element value)
        `(setf (chain ,element inner-text) ,value))
      
      (defmacro set-method (object method-name args &body body)
        `(setf (chain ,object ,method-name)
               (lambda ,args ,@body)))
      
      (defvar create-counter-div 
        (lambda (&optional (n 0))
          (let ((elm (chain document (create-element "div"))))
            (inner-value elm n)
            (set-method elm increment (&optional (x 1))
                        (incf n x)
                        (inner-value elm n))
            elm)))
      
      (defvar counter (create-counter-div 3))
      (chain document body (append-child counter))
      (chain counter (increment 4))) this)))

出力されたJavaScript

(function (global) {
    var createCounterDiv = function (n) {
        if (n === undefined) {
            n = 0;
        };
        var elm = document.createElement('div');
        elm.innerText = n;
        elm.increment = function (x) {
            if (x === undefined) {
                x = 1;
            };
            n += x;
            return elm.innerText = n;
        };
        return elm;
    };
    var counter = createCounterDiv(3);
    document.body.appendChild(counter);
    return counter.increment(4);
})(this);

感想

  • chainが便利。かなり素直に書ける
  • &optionalとか使えるのがアツい
  • 便利だこれ…(直感)
広告を非表示にする