2008年12月26日金曜日

'fooと#'fooの違いは環境の違い

QUOTEとFUNCTIONを使い分ける:もう少し詳しい説明 -- 逆引きCommonLisp

をぼんやり見ていて、そういえば FUNCTION と COERCE では環境が違うってうちの CLISP が言ってたのを思い出した。

(funcall '(lambda (x) (1+ x)) 1)
;; => Error
;; *** - FUNCALL: argument (LAMBDA (X) (1+ X)) is not a function.
;;       To get a function in the current environment, write (FUNCTION ...).
;;       To get a function in the global environment, write (COERCE '... 'FUNCTION).

CLHS にもそれらしい記述があったのでメモ。

CLHS: Function COERCE
If the result-type is function, and object is any function name that is fbound but that is globally defined neither as a macro name nor as a special operator, then the result is the functional value of object. If the result-type is function, and object is a lambda expression, then the result is a closure of object in the null lexical environment.

= objectがマクロ、スペシャル・オペレータ以外でグローバルに定義されたfboundな関数名を持っているなら、そのobjectの関数値を返す。 objectがラムダ式なら、空レキシカル環境なobjectのクロージャを返す。

CLHS: Special Operator FUNCTION
The value of function is the functional value of name in the current lexical environment.

= functionは現在のレキシカルな環境を持ったnameの関数値を返す。

この環境を引数に渡せる関数、マクロはいくつかある。 ラムダリストキーワードに &environment を持つものがそれで、例えば setf とか push とか。

(arglist 'setf)
;=> (&WHOLE SYSTEM::WHOLE-FORM &REST SYSTEM::ARGS &ENVIRONMENT SYSTEM::ENV)

macroexpand も環境を引数に渡せるが、こちらはなぜか &optional になっている。

(arglist 'macroexpand)
;=> (FORM &OPTIONAL ENV)

2008年12月19日金曜日

あと一週間

万能プレーヤーである VLC media player の起動アイコンがクリスマス仕様になっている事に気づいた。

Google のトップページが時期によって模様替えされるのは何度か見たけれど、こういう遊びをするソフトウェアって少ないんじゃないかな。

2008年12月18日木曜日

おおえまくすよ マクロをつかわないとは なさけない

(require 'cl) は悪なのか?の続き。

気が向いたので軽く調べてみた。

---

GNU Emacs のマニュアルには以下のような注意書きがある。

Common Lisp Extensions Overview
Please note: the CL functions are not standard parts of the Emacs Lisp name space, so it is legitimate for users to define them with other, conflicting meanings. To avoid conflicting with those user activities, we have a policy that packages installed in Emacs must not load CL at run time. (It is ok for them to load CL at compile time only, with eval-when-compile, and use the macros it provides.) If you are writing packages that you plan to distribute and invite widespread use for, you might want to observe the same rule.

どうやら Emacs 実行時の cl パッケージ読み込みは禁止されていて、コンパイル時のマクロ展開に限って読み込みを許可しているらしい。つまり (require 'cl) ではなくて (eval-when-compile (require 'cl)) を使いなさいってことか。

コンパイル時にマクロ展開をしなければならないのは Common Lisp でも同じだから、この点は納得。

the CL functions are not standard parts of the Emacs Lisp name space

これは「名前には接頭辞を付けろ」という elisp のコーディング規則に反しているから関数上書きしないように気をつけてね、そんな意味でしょうか。

んー、名前空間の話は cl パッケージを読み込んだ時点でユーザは承知していると思うんだが。

ありがたいことに elisp のマクロは CL のマクロと(ほぼ)同等の機能があるのだから、マクロによる抽象化によってソースコードが短く、より簡素になるなら利用しない手はない。見やすいコードとそうでないコード、どっちを書きたい?と質問するようなもの。

…というのが自分の意見です。

所詮 .emacs 位しか書いたことのない学生の意見ですから反論は認めます。mew の作者さんのように何かしらの elisp パッケージを作成した方は、きっとこの cl パッケージのおかげで互換性等々に苦労されたんだと思いますし。

まとめ、あるいは教訓:

  • cl パッケージを利用するファイルは、必ずコンパイルして実行時にそれに依存しない形にすること。(eval-when-compile (require 'cl)) 用法用量を守って正しく使いましょう。
  • マニュアルはなるべく最新のものを参照すること。OSS のマニュアルの邦訳がメンテナンスされないのはよくある話。

おまけ:

「マクロではなく cl.el の関数群を使いたいときはどうすれば?」と質問された時の Stallman 氏の返事

Installed Lisp packages must not use those functions.

2008年12月15日月曜日

[Emacs] newLISP 環境を構築してみたい

[追記@2009-04-20T03:29:05]
もう少し拡張したものを置いておきます。
http://github.com/kosh04/newlisp-files/tree/master


Emacs から newLISP のプロセスを操作する関数群を書いたのでちょっと醸してみます。

もともと xyzzylisp 用に作ったんだけど、やっぱり資産がある elisp の方が短くできてしまったので。

使い方:

  1. 以下の URL のコードを newlisp.el で保存して eval-buffer
    http://paste.lisp.org/display/72178
  2. 必要に応じて、以下の変数を書き換え。
    • newlisp バイナリのパス名: *newlisp-command*
    • プロセス間文字コード: *newlisp-process-coding-system*
    Windows インストーラから newLISP をインストールした場合は、環境変数 NEWLISPDIR が設定されているはずなのでパス名はそのままで大丈夫だと思います。 newlisp バイナリには utf-8 が使える版と使えない版があるので、ここではマルチバイト文字を扱える utf-8 版を推奨します。 →[xyzzy] newLISP 環境を構築してみたい
  3. 後のS式操作はカンでお願いします。スクラッチバッファで遊んだことがある人ならたぶん分かるでしょう。 主に使うのは newlisp-eval-last-sexpnewlisp-show-repl くらいでしょうか。

残念ながらハイライトなどのリッチな機能はありません。

あ...それと (require 'cl) が必要かもしれないです。


Emacs(VineLinux4.2) だとなぜか評価のタイミングがずれました。
(一回目の newlisp-eval-last-sexp では何も表示されないで、二回目でまとめて出力されてしまう)
NTEmacs では問題なく動いたので、 Emacs 側の問題?

(require 'cl) は悪なのか?

自分の中で Emacs Lisp の CL パッケージの評判があまりよろしくない印象なんだが、はっきりした理由を聞いたことがないなあ。

気が向いたらまとめのページが無いか詮索してみよう。

詮索した。

elisp にもラベル記法があったのか

(let ((print-circle t)
      (lst '(7 8 3 9 4 0 1 6 5 2)))
  (princ (list (sort lst #'<) lst))
  (terpri))
;-> ((0 1 2 3 4 5 6 . #1=(7 8 9)) #1#)
;=> t

((lambda (x)
   ((lambda (y1)
      ((lambda (&optional y2)
         ((lambda (z)
            (list x y1 y2 z))
          (car (cdr #1=(cdr #2='(1 (2) 3))))))
       (car (cdr #3=(car #1#)))))
    (car #3#)))
 (car #2#))
;=> (1 2 nil 3)

;; 上のコードは少し古い destructuring-bind のマクロ展開 (CL)
(macroexpand '(destructuring-bind (x (y1 &optional y2) z) '(1 (2) 3)
                (list x y1 y2 z)))

Emacs Lisp はかなり Common Lisp に近い仕様なんだなと改めて思った。
# もちろん動的スコープだとかクロージャが無いとかツッコミはあるけど :-)

しかしこの書き方 (#n#) の名称なんだっけ...


CLtL2, CLHS をパッと見たけど 「#n# 構文 (#n# syntax)」としか書いてなかった。

あと、影響を受けたのは CL より Maclisp 方言か。歴史知らなさ過ぎかもしれない。

2008年12月12日金曜日

newLISP v.10.0 インストールメモ

newLISP v.10.0 がリリースされていました

newLISP v.10.0 Release Notes

インストーラも用意されていますが、せっかくなので野良ビルドしてみようかなと 以下手順を列挙

Makefile の typo 修正:
41c41
<  @echo "  make linux           # newlisp for LINUX
---
>  @echo "  make linux           # newlisp for LINUX"
47,48c47,48
<  @echi "  make debian          # newlisp for UBUNTU debian with readline support"
<  @echi "  make debian_utf8     # newlisp for UBUNTU debian UTF-8 with readline support"
---
>  @echo "  make debian          # newlisp for UBUNTU debian with readline support"
>  @echo "  make debian_utf8     # newlisp for UBUNTU debian UTF-8 with readline support"

Win32の場合

MinGW-5.1.4 gcc を使う (cygwin gcc ではコンパイル出来なかった) makefile_mingw (or makefile_mingw_utf8) の書き換え (cygwinインストールしてなかったら /cygdrive ではないよなあ...)
CC=/cygdrive/c/MinGW/bin/gcc
STRIP=/cygdrive/c/MinGW/bin/strip

> make mingw_utf8

Linux (Vine 4.2)の場合

# apt-get install readline-devel  ; 必要ならば

$ make linux_utf8_readline
$ make test
$ make install_home

まあ特につまづくような所はないです

出来上がった newlisp バイナリのサイズは 200Kバイト前後とかなり小さい ソースファイルも少ないから、頑張れば読めるかもね

でもインデントがすげー独特なんですが…

2008年12月7日日曜日

あったら便利だと思う

パス名も setf で変更できたらいいのに

(setf (pathname-type "/home/kosh/foo.l") "lsp")
;=> "/home/kosh/foo.lsp"

パッと思いついたもの。 これだと置換する文字数同じでないとダメだし、ファイル名とディレクトリ名が被ると面倒だなあ。やはり make-pathname だろうか

(and (setf (subseq #1="C:/home/xyzzy/xyzzy.exe"
                   (search (pathname-type #1#) #1#))
           "mp3")
     #1#)
;=> "C:/home/xyzzy/xyzzy.mp3"

[追記] 2008-12-13T20:16:43+09:00

初めから make-pathname 使おうと思わないのはまだCL脳でない証拠か

(make-pathname :defaults "C:/home/xyzzy/xyzzy.exe"
               :type "mp3")
;=> #P"C:\\home\\xyzzy\\xyzzy.mp3"

パスネームの構造をよく知らないんだが、リストもしくは構造体で表現されているならば setf が使えてもいい気がするのになあ こんな感じに

((:device "C")
 (:host nil)
 (:directory ("home" "xyzzy"))
 (:name "xyzzy")
 (:type "exe")
 (:version nil))

ま、あくまでも仮定の話だしね

2008年12月6日土曜日

メモ化のメモ

再帰関数を効率の良いコードに直せ、と言われてパッと思い浮かぶのが

  • ループに還元
  • 末尾再帰
これくらいしかないと思ってた。メモ化なんて選択肢もあるのね。

計算コストの高い関数を同じ引数で複数回呼び出したいときがあるなら,値をメモワイズ(memoize)しておくのが得だ.以前の返り値をみな保管しておき,関数が呼び出される度にまず保管場所を見て,値が既に得られていないか調べる.

memoize 関数のコードはここでは省くとして、ハッシュをキャッシュに使えるのかと知ってひとつ賢くなった気分。

;; ベタベタな2重再帰フィボナッチ定義
(setf (symbol-function 'fibo)
      (memoize #'(lambda (n)
                   (cond ((= n 0) 0)
                         ((= n 1) 1)
                         (t (+ (fibo (- n 1))
                               (fibo (- n 2))))))))
(time (fibo 1000))     ; on xyzzy
Real time: 3.219 sec.
;=>4346655768693745643568852767504062580256466051737178040248172908
   9536555417949051890403879840079255169295922593080322634775209689
   6232398733224711616429964409065331879382989696499285160037044761
   37795166849228875

速っ!

ここでもうひとつ、最近自分が弄っている newLISP でもメモ化を使う方法があるのでご紹介を。 といっても newLISP にはハッシュ関数がないのでコンテキストで代用ですが。

Speed up with memoization -- newLISP Code Patterns
(define-macro (memoize mem-func func)
  (set (sym mem-func mem-func)
       (letex (f func  c mem-func)
         (lambda ()
           (or (context c (string (args)))
               (context c (string (args)) (apply f (args))))))))

(memoize fibo (lambda (n)
                (cond ((= n 0) 0)
                      ((= n 1) 1)
                      (true (+ (fibo (- n 1))
                               (fibo (- n 2)))))))
(time (fibo:fibo 100))
;=>0

newLISP のコンテキストは Common Lisp の簡易版パッケージ機能と考えていいでしょう。たぶん。 ここではキャッシュ用に名前空間 fibo を作成して、そこに関数値を放り込んだり参照したりしてます。

;; コンテキスト使用例
; 検索
(context MAIN "define")         ;=> define <407ee9>
(context MAIN "kosh")           ;=> nil

; 登録
(context MAIN "kosh" "lisper?")
(context MAIN "kosh")           ;=> "lisper?"
※ただし戻り値の正確さは (fibo:fibo 90) くらいで限界なんですが…多倍長整数に弱い?