Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2007-12-20

40年前の処理系 PDP-1 Lisp を試してみる

| 05:24 | 40年前の処理系 PDP-1 Lisp を試してみる - わだばLisperになる を含むブックマーク はてなブックマーク - 40年前の処理系 PDP-1 Lisp を試してみる - わだばLisperになる

何となく前から気になっていたPDP-1 Lispを試してみることにしました。

PDP-1 Lispは、LISPの処理系の中でもかなり初期の部類で、1964年に当時高校生だった、L. Peter Deutschが作った処理系です。

その名の通り、PDP-1で動きます。

PDP-1のエミュレータはSIMHというエミュレータ集の中に含まれていることは以前から知ってはいたのですが、しかしさすがにPDP-1ということもあり、きっと動作させるのはかなり面倒なんだろうと思って放置していたのですが、環境が整備されていたこともあり実際に試してみると非常に簡単でした。

とりあえず用意するものとしては、

  1. PDP-1 エミュレータSimH ”Classic”
  2. Lisp for the PDP-1

位のものです。

simhは、ubuntuや、debianではパッケージになっているようです。

PDP-1 Lispの準備

lispswre.zipに同梱のlisp_doc.txtを読んで、必要なものを作成します。

といっても

展開したディレクトリで、

$ cc macro1.c -o macro1
$ macro1 lisp.mac

と実行するのみです。

起動スクリプトの作成

  • lisp.boot
set cpu mdv
load lisp.rim
d extm_init 1
run

d tw 7777
c

d tw 400
c

d ss 2
save lisp.sav
c

として作成してみます。

起動用スクリプトの作成

  • pdp-1-lisp
#!/bin/sh

cd /share/sys/emu/PDP-1.emu/
./pdp1 ./lisp.boot

適当に起動用のスクリプトを作ってみます。

とりあえず、これで遊べます。

(plus 3 3) |スペース| |改行|
;=> 6

式を入力して、スペースを入力し改行すると評価される様です。

Emacsと連携させてみる。

run-lispで使えるように適当に設定してみます。

(setq inferior-lisp-program "~/bin/pdp-1-lisp")

等々

inferior-lisp-modeだと、式の後の一文字スペースが足りないので、若干無理矢理ながら関数に細工します。といっても一文字空白を挿入してから改行するようにするだけです。

(defun lisp-eval-region (start end &optional and-go)
  "Send the current region to the inferior Lisp process.
Prefix argument means switch to the Lisp buffer afterwards."
  (interactive "r\nP")
  (comint-send-region (inferior-lisp-proc) start end)
  (comint-send-string (inferior-lisp-proc) " \n")
  (if and-go (switch-to-lisp t)))

不正な入力があると、PDP-1ごと落ちますが、simhのモニタに落ちたらcを入力すると復帰します。

SIMHの終了には、Control-Eを入力し、モニタに落ちて、qの入力で終了します。

Emacs内部の場合、Control-Qでエスケープする必要ありです。

色々探って遊ぶ

  • Hello, World!
(print (quote Hello\,\ World!))
;=> Hello, World! 

とりあえず、Hello, World! 文字列というものは無いようなので、シンボルです。シンボルの大文字と小文字を区別するというのが意外です。

  • 関数を定義してみる

関数の定義する方法が全く分からず、結構悩みました。

シンボルは、oblistというリストに格納されていて、起動時には、

(atom car cdr cond cons eq gensym greaterp list minus 
numberp stop prin1 quotient rplaca rplacd terpri prog return
 go setq sassoc plus times logand logor xeq loc null quote
 lambda apval subr expr fsubr fexpr t oblist read eval
 print nil) 

となっていてこれで全部です。

標準で使える関数もこのなかにあるということになるのですが、defineとかcsetqとかそれらしきものはなく、結構悩んだのですが、どうにもならないので、マニュアルを探したところPDFのマニュアルがありました。

これによると、どうやら関数は、rplacdを使って定義するらしいのです。

どういう仕組みなのかというと、シンボルのcdrがシンボルのプロパティになっていて、

(cdr (quote foo))
;=> nil

のようになります。それで、シンボルのCDR部に関数定義を詰め込むと名前に関数が設定できるという仕組みのようです。こんなの良く考えつくなと感心。

ということで、定番のfibは、

(rplacd (quote fib)
	(quote 
	 (expr
	  (lambda (x)
	    (cond ((greaterp 2 x) x)
		  (t (plus (fib (plus x (minus 1)))
			   (fib (plus x (minus 2))))))))))

(fib 10)
;=>25 (8進数)

と定義できました。再帰も使えます。引き算用の関数はないので負数を足すということになっていて、lesspもないのでgreaterpを逆転させてます。数字は8進数です。

exprというのは、固定の引数を取る関数であることを表わしています。他にfexprというのもありこっちは、可変引数です。

  • reverse
(rplacd (quote reverse)
	(quote (expr
		(lambda (lst)
		  (prog (l res)
		     (setq l lst)
		     loop 
		     (cond ((null l) (return res)))
		     (setq res (cons (car l) res))
		     (setq l (cdr l))
		     (go loop))))))

progでreverse

  • mapcar
(rplacd (quote mapcar)
	(quote (expr
		(lambda (f lst)
		  (prog (l res)
		     (setq l lst)
		     loop
		     (cond ((null l) (return (reverse res))))
		     (setq res (cons (f (car l)) res))
		     (setq l (cdr l))
		     (go loop))))))

(print
(mapcar (quote (lambda (x) (times 3 x)))
	(quote (1 2 3))))
;==>
;(3 6 11) 

なんとなくmapcarを作ってみたり。

なんとなくまとめ

「空リストが偽を表わす」というのはどの辺りから始まったのかというのは、あまりはっきりしていないようです。

最初期のLisp 1.5でも真と偽は、TとFとして定義されている様子て、「空リストが偽を表わす」ことも肯定的に捉えられてはいなかったようで、Interlisp文化以外のところでは、むしろ否定的に考えられていたようです。

それで、このPDP-1 Lispはどうなのかと言えば、()も、(quote ())もnilで偽ということになっています。

(cdr ni)はどうかといえば、(apval nil)がでてきます。これは、nilのプロパティってことなんでしょう。

ちなみに(cdr nil)としても、エラーにならず、nilを返すというのは、Interlisp由来らしいのですが、処理系の発展の流れからすると、PDP-1 Lisp -> BBN Lisp -> Interlispという流れで、Peter Deutschがからんでいるので、もしかしたらこの辺は、この人由来なのかしらと、ぼんやり考えたりしました。

2007-11-14

Lispm Font

| 05:17 | Lispm Font - わだばLisperになる を含むブックマーク はてなブックマーク - Lispm Font - わだばLisperになる

ああ、毎日更新することが目標だったのに間があいてしまった!

とりあえず、小ネタでXで使えるLispマシン(以下、LispM)のフォントを紹介してみます。

とはいえ、こちらの配布サイトの解説の通りにすればすぐ使えます。

LispMのフォントはもちろんこれだけではなくて他にも色々ありますが、これが標準で、CPTFONTという名前です。

個人的には、このフォントだと全部大文字で書かれたLispのコードでも読み易かったりするところが気に入っています。

また、Fontforgeで、無理矢理TrueTypeフォントに変換したりして使ってみてますが、それも乙です。

2007-11-09

StumpWM (2)

| 23:46 | StumpWM (2)  - わだばLisperになる を含むブックマーク はてなブックマーク - StumpWM (2)  - わだばLisperになる

StumpWMをインストールしてから一ヶ月位常用してみていますが、もとからWMに高機能を求めていないせいか別段不自由することもなく使えています。

この間に使ってみて分かった細々としたことを書いてみたいと思います。

WMの終了方法

前回WMの終了の方法さえ分からず、Lisp式を評価する方法で、(cl-user::quit)などとしていましたが、プレフィクスキー(EmacsのCtrl-xのようなもので、デフォルトでは、Ctrl-t)+;でコマンド入力のところで、quitを実行するらしいことが分かりました。

StumpWM内部のコマンドを定義する

下準備

定義するコマンドは、~/.stumpwmrcに記述するのが一般的かと思います。下記の例は、全部、~/.stumpwmrcに記述しています。

定義する

define-stumpwm-commandによって定義できます。emacsを実行するコマンドは下記のように定義できます。

(define-stumpwm-command "emacs" ()
  (run-or-raise "emacs-22.0.50" '(:class "Emacs")))

run-or-raiseは、ウィンドウを最前面に持ってくるか、実行されていない場合プロセスを実行します。

他にも色々方法はあるようです。

コマンドにショートカットキーを定義する

define-keyで定義します。下記の例では、通常のプレフィクスキー+C-eでStumpWM内で定義したコマンドemacsを実行するようにしています。

(define-key *root-map* (kbd "C-e") "emacs")

フォント

set-fontで指定可能です。UTF-8環境でフォントのエンコードをiso10646-1にすれば、日本語も化けないで表示されます。

(set-font "-kochi-gothic-medium-r-normal-*-*-110-*-*-p-*-iso10646-1")

便利なところ

最新のStumpWMには、内部のコマンドをシェルから実行できる、stumpishというシェルスクリプトが附属してきます。

これを使用するとウィンドウの切り替えを自動で実行できたりするので、例えば、emacsclient等と組み合わせると便利です。

#!/bin/sh

stumpish emacs #StumpWM内のコマンド
emacsclient $*
stumpish other #直前にフォーカスがあったウィンドウにフォーカス

exit 0

上記は、

  1. stumpish emacsで、Emacsのウィンドウにフォーカスし、
  2. emacsclientを実行し、
  3. 終了したら、元のウィンドウにフォーカスを戻す

という内容です。

同じようにslimeからHyperSpecを呼び出すついでにfirefoxにフォーカスするようなものも便利です。

その他、小物

(define-stumpwm-command "init" ()
  (stumpwm::load-rc-file))

設定を変更して、初期化ファイルを読み込ませるのが面倒なので定義してみています。

困っていること

一番良く使うアプリケーションはfirefoxなのですが、どうやらEmacsや、他のアプリケーションのようにウィンドウに一定の名前が付かないせいか、run-or-raiseだと実行する度に新しいfirefoxのプロセスが実行されてしまいます。

その場しのぎの対策

firefoxのウィンドウには、ffという名前を付けて、プロセスが実行されているならウィンドウの切り替えだけ、プロセスが起動されていないならば、firefoxを起動という風にしてみています。firefoxのウィンドウへの名前づけは、WMが起動するたびに手動で行っているので、若干面倒なものがあります。

run-or-raiseは、WM内のWindowの名前を見て判断しているので、プロセスを見て判断するものを定義して使ってみています。

;; psを実行して、firefox-binの文字列をgrep
(defun get-firefox-ps (&optional (owner "g000001"))
  (let ((ps (with-output-to-string (out)
	      (sb-ext:run-program "/bin/ps" '("-ef")
				  :environment '("LANG=C")
				  :output out)))
	(scn (cl-ppcre:create-scanner (format nil "~A.*firefox-bin" owner))))
    (with-input-from-string (str ps)
      (series:iterate ((line (series:scan-stream str #'read-line)))
		      (when (cl-ppcre:scan scn line)
			(return-from get-firefox-ps line))))))

;; get-firefox-psを実行し、ffというウィンドウに切り換えるか、
;; run-or-raiseでfirefoxを起動。
(defun run-or-raise-firefox ()
  (if (get-firefox-ps)
      (select-window (current-group) "ff")
      (run-or-raise "firefox" '(:class "Firefox" :title "Firefox"))))

;; ffでfirefoxを実行するStumpWMのコマンド定義
(define-stumpwm-command "ff" ()
  (run-or-raise-firefox))

まとめ

まだまだ使い方も分っていないStumpWMですが、Common Lispで拡張できるというのはやはり面白くこれからも掘り下げてみたいと思っています。

2007-11-08

asdf-addonsで楽をする

| 20:00 |  asdf-addonsで楽をする - わだばLisperになる を含むブックマーク はてなブックマーク -  asdf-addonsで楽をする - わだばLisperになる

asdfasdf-installはとても便利で、単一の実装を単一のOSで使う分には最高なんですが、素のasdfでは、faslファイルをasdfのソースのディレクトリに出力するので、そのディレクトリを異なるプラットホームで共有させようと思うとちょっと厄介だったりします。

Lispのソースファイルはアーキテクチャに依存しないので各種OS/lisp間で共有のNFSディレクトリ等に設置し、アーキテクチャに依存したfaslファイル等は、は別ディレクトリに格納したらもっと楽に共有できるのになあとずっと考えていましたが、common-lisp.netのプロジェクトをつらつらと眺めていたら、asdf-addons projectがまさにこの問題を解決するもののようだったので早速試してみることにしました。

設定

今のところ、asdf-addonsが提供しているものは、asdf-cacheというものだけのようです。これが、faslファイルの出力先を変更するという機能を実現します。

各ファイルの配置方針は、

  • /share/sys/cl/src
    • asdfインストール可能なファイルも可能でないファイルもソースファイルはこの場所に置く
  • /share/sys/cl/asdf
    • asdfのasdファイルを置く
  • /share/sys/cl/fasls
    • 各アーキテクチャに依存したfaslファイルが格納される

という風に決めて、adsfや、adsf-installのパス設定等を変更してみます。

ちなみに、これが正しい方法かどうかは不明です…。

asdf::*central-registry*にasdファイルが置かれたパスを設定するので、

(setq asdf::*central-registry* '(#p"/share/sys/cl/asdf/" その他~))

として上記のパスを一番最初に検索するようにします。

asdf-install:*locations*がadsf-installでインストールする先を設定しているようなので、上記のパスをシステムグローバルなパスとして設定します。

(setq asdf-install:*locations* 
      '((#P"/share/sys/cl/src/" #P"/share/sys/cl/asdf/" "System-wide install")
	(#Pホームディレクトリ等~ "Personal installation"))

asdf-installの際にシステム全体か、ホームディレクトリかを訊かれますが、この変数を参照しているようです。

リスト内部の各項目がインストール先の場所で、

'((ソースの場所 asdファイルの場所 名前) ~繰り返し~)

の様になっていて、任意の数の場所を設定します。

(setq asdf-cache:*asdf-cache* #p"/share/sys/cl/")

これが、今回導入したasdf-addonsのasdff-cacheがfaslファイルを出力する場所になります。asdf-cache内部で、このディレクトリをベースディレクトリとして、.faslsというディレクトリを作成し、そこにさらにOSや処理系のバージョンごとに異なるディレクトリを作成しファイルを配置してくれます。デフォルトでは、.faslsですが、自分の環境では隠す必要もないので、ソースを変更してfaslsという名前で作るようにしました。

asdf-cacheのインストール

asdf-cacheのインストールは、直接ファイルを読み込ませて行ないます。

(load "/share/sys/cl/src/asdf-cache.lisp")

動作の確認

まず、自分のメインの処理系である、SBCL/Linuxで試してみました。

sbclは、asdfasdf-installも完備しているので楽です。

(asdf-install:install :foo)

とすると、上記で設定したインストール先を訊かれ、設定した場所にソースファイルが展開され、asdディレクトリにリンクが張られます。

この後は、asdf-installは、asdfを呼び出してコンパイル等が始まりますがasdf-cacheの働きで、

/share/sys/cl/fasls/sbcl-linux-x86-1.0.10/share/sys/cl/src/foo

というようなディレクトリが作成され、その下にfaslファイルが出力されます。

そして、faslファイルの読み出しもasdf-cacheの働きで、指定したディレクトリより読み出すようになります。

adsf-cacheが便利なところ

共有している場合、どこか一つのマシンで、asdf-installすると、共有しているasdのディレクトリにもasdファイルがリンクされるため、他のマシンでも、

(asdf:oos 'asdf:load-op :foo)

とすれば、コンパイルとロードが完了するため、asdf-installは一回で済みます。これが案外便利です。

また、asdfに対応したソースならば、asdf::*central-registry*にリンクを張るだけでasdfで導入できますが、この場合ソースディレクトリにfaslファイルがばら蒔かれることもないので快適です。

今のところ、処理系は、SBCL、CLISP、CMUCL、Allegro 8.1試用版、Lispworks 5.0.1試用版、ECL、OSはLinux、MacOSXで試してみましたが、全部の環境を簡単に揃えることができるので結構お勧めです。

2007-11-06

Series (3) / generatorとgatherer

| 01:34 |  Series (3) / generatorとgatherer - わだばLisperになる を含むブックマーク はてなブックマーク -  Series (3) / generatorとgatherer - わだばLisperになる

今回は、Seriesと同じく配布されていて、また同じくCLtL2の巻末にも関連して収録されている、ジェネレータとギャザラを試してみることにしました。

generator

ジェネレータは、シリーズの列を順番に取り出す機構のようです。

(generator シリーズ)

とすると、ジェネレータが生成されます。

生成された、ジェネレータは

(next-in ジェネレータ 空になったときのアクション)

で順番に取り出すことができ、空になると、指定したアクションを実行します。

(defun doとgeneratorをつかったfizzbuzz ()
  (do ((gen (generator (scan-range :from 1 :upto 100))))
      (())
    (let* ((i (next-in gen (return)))
	   (fizz (zerop (mod i 3)))
	   (buzz (zerop (mod i 5))))
      (print (cond ((and fizz buzz) "FizzBuzz")
		   (fizz "Fizz")
		   (buzz "Buzz")
		   ('T i))))))

無理矢理な感じですが、FizzBuzzを作ってみました。doは無限ループと変数束縛のために使っています。

gatherer

ギャザラはジェネレータの逆で、

(gatherer コレクタ) ;コレクタは、collect系の関数

結果を溜め込む機構であるギャザラを生成し、

(next-out ギャザラ)

で指定したギャザラに溜め込み、溜め込んだものは、

(result-of ギャザラ)

を呼ぶことで、結果として返せます。

(defun remq (item list &key (count -1))
  (let ((res (gatherer #'collect)))
    (iterate ((l (scan list)))
      (cond ((zerop count) (next-out res l))
	    ((eq item l) (decf count))
	    ('T (next-out res l))))
    (result-of res)))

(remq 'x '(x x x x x foo x) :count 2)
;=> (X X X FOO X) 

gathererを使ってeqで要素を比較するremoveを作ってみました。

gathering

gathererは出力が一つですが、gatheringは複数を切り換え出力できるところが違い、また、本体から抜けると自動で結果が返されます。

(gathering ((変数 コレクター) (変数 コレクター)) ~本体~)
(defvar hiyoko '(♂ ♀ ♂ ♀ ♂ ♀ ♂ ♀ ♂ ♀ ♂ ♂ ♀ ♂  ♂ ♀ ♂ ♀ ♀ ♂ ♀ ♀))

(gathering ((m collect) (f (lambda (x) (collect 'vector x))))
  (iterate ((i (scan hiyoko)))
    (case i
      ((next-out m i))
      ((next-out f i)))))
=>
(♂ ♂ ♂ ♂ ♂ ♂ ♂ ♂ ♂ ♂ ♂), #(♀ ♀ ♀ ♀ ♀ ♀ ♀ ♀ ♀ ♀ ♀)

ひよこの選別をすると考えて、♂はリストで、♀はベクタの2値を返しています。

2007-11-03

Series (2)

| 22:48 |  Series (2) - わだばLisperになる を含むブックマーク はてなブックマーク -  Series (2) - わだばLisperになる

今回もSeriesがどんなものなのか色々試してみています。

実際の利用事例をGoogle Codeを使って検索してみるのですが、利用例は見当らずで、見付かるものといえばSERIES自体のソースコード位です。

CLTL2では、map-fnを#M、seriesを#Zとリードマクロ文字で定義して表記してあります。

これは、表記としても使う上でも便利だなと思うのですが、自分で定義しないといけないんだと思ってコードを検索してみたら、SERIESのソース自体に定義がありました(*'-')

(series::install)

を実行することで使えるようになるらしいです。が、#Mが自分の手元だと上手く機能しません…。何が間違っているのだろうか…。

色々試してみる

自分の予想では、SERIESはSRFI-42に非常に近い使い勝手ではないだろうか思い、SRFI-42の使用例をSERIESに翻訳してみることにしました。ちなみに自分は、SRFI-42の使い方も良く分かっているわけではありません…。

とりあえずウェブで見付けてきた題材と翻訳を列記してみます。

doukaku.org:九九の表示よりdo-ecの例
;; doukaku 
(define (display99 n)
  (do-ec (: x 1 (+ n 1)) (: y 1 (+ n 1))
         (format #t "~d * ~d = ~2d~%" x y (* x y))))
;; SERIESで
(defun display99 (n)
  (iterate ((x (scan-range :from 1 :upto n)))
    (iterate ((y (scan-range :from 1 :upto n)))
      (format t "~D * ~D = ~2D~%" x y (* x y)))))

適当なマクロですが、

(defmacro iterate* (binds &body body)
  `,(reduce (lambda (b res)
	      `(iterate (,b) ,res))
	    binds
	    :initial-value `(progn ,@body)
	    :from-end 'T))

のようにすれば、

(defun display99 (n)
  (iterate* ((x (scan-range :from 1 :upto n)) 
             (y (scan-range :from 1 :upto n)))
    (format t "~D * ~D = ~2D~%" x y (* x y))))

と書けてより近いような気もしましたが、気休めな気もします。

上記を任意の数に拡張した版
(define (displayNN n)
  (let ((w0 (string-length (number->string n)))
        (w1 (string-length (number->string (* n n)))))
    (do-ec (: x 1 (+ n 1)) (: y 1 (+ n 1))
           (format #t "~vd * ~vd = ~vd~%" w0 x w0 y w1 (* x y)))))
;; SERIESで
(defun displayNN (n)
  (let ((w0 (length (princ-to-string n)))
	(w1 (length (princ-to-string (* n n)))))
    (iterate ((x (scan-range :from 1 :upto n)))
      (iterate ((y (scan-range :from 1 :upto n)))
	(format t "~VD * ~VD = ~VD~%" w0 x w0 y w1 (* x y))))))
doukaku.org:隣り合う二項の差よりlist-ecの例
(define (diff xs) (list-ec (:parallel (: x xs) (: y (cdr xs))) (- y x)))
;; SERIESで
(defun diff (xs) 
  (collect (mapping ((x (scan xs)) (y (scan (cdr xs)))) (- y x))))
;;
(diff '(2 1 4 3 6 5 7))
;-> (-1 3 -1 3 -1 2)

SRFI-42ではデフォルトで入れ子になり:parallelを指定することにより並列になるそうですが、SERIESはデフォルトが並列で、入れ子は手作りになります。

doukaku.org:ダブル完全数よりsum-ecの例
(define (double-complete-number? n)
  (= (* n 3)
     (sum-ec (: i 1 (+ 1 n))
             (if (zero? (remainder n i)))
             i)))

(do-ec (: i 1 10001)
       (if (double-complete-number? i) (print i)))
;; SERIESで
(defun double-complete-number-p (n)
  (= (* n 3)
     (collect-sum 
      (choose
       (mapping ((i (scan-range :from 1 :upto n)))
	 (when (zerop (rem n i)) i))))))

(iterate ((i (scan-range :from 1 :upto 10000)))
  (when (double-complete-number-p i)
    (print i)))

まとめ

というようにSRFI-42をSERIESに変換してみましたが、結構何の捻りもなしに素直に変換できるようです。関数/マクロの名前の付け方にも非常に共通点が多いというのも理由の一つかもしれません。

2007-11-02

Series (1)

| 20:39 |  Series (1) - わだばLisperになる を含むブックマーク はてなブックマーク -  Series (1) - わだばLisperになる

CLTL2の巻末の付録にも載っていて非常に魅力的にも見えるseriesですが、全然使い方が分からないので、loopの解説と対照させつつ機能を散策してみることにしました。

下記のloopマクロを解説したサイトさんを参考にさせて頂きました。

こちらの方々のloopの事例を拾ってSERIESに変換してみています。

元々使い方が分ってないので、妙なところもあるんじゃないかと思います。

下準備

SERIESはasdf-install可能です。

(asdf-install:install :series)

いろいろなケースをSERIESで処理してみる

(use-package "SERIES")
リスト
(loop for i from 10 to 50 by 5 collect i)
;==> (10 15 20 25 30 35 40 45 50)

;; SERIESで
(collect (scan-range :from 10 :upto 50 :by 5))

まず、SERIESの作法としては、scan~でシリーズと呼ばれる列を生成し、collect~や、mapping、iterateでシリーズを加工するという流れのようです。

上では、scan-rangeでシリーズを作成し、collectでシリーズのアイテムを集めてリストに変換しています。

(loop for x in '(1 2 3 4) by #'cddr collect x) 

;; SERIESで
(collect
    (choose (series t nil)
	    (scan '(1 2 3 4))))
;==> (1 3) ; 一つ飛ばし

一つ飛しというのが良く分からず、chooseで選択しています。

(series t nil)で、tとnilが無限に続いたシリーズを作成し、それと、(scan '(1 2 3 4))を重ね合せることによってtの部分だけ拾っています。

(loop for x on '(1 2 3) collect x) 
;==> ((1 2 3) (2 3) (3))

;; SERIESで
(collect (scan-sublists '(1 2 3)))
(loop for x on '(1 2 3 4 5) by #'cddr collect x)
;==> ((1 2 3 4 5) (3 4 5) (5))

;; SERIESで
(collect
  (choose (series t nil)
	  (scan-sublists '(1 2 3 4 5))))
ハッシュ
;; 下準備
(defvar ht (make-hash-table))
(setf (gethash 'foo ht) 1)
(setf (gethash 'bar ht) 2)
(loop for x being the hash-keys in ht collect x)
; ==> (BAR FOO)

;; SERIESで
(collect (scan-hash ht))
(loop for x being the hash-keys in ht using (hash-value y) collect (cons x y))
;==> ((BAR . 2) (FOO . 1))

;; SERIESで
(collect
  (mapping (((k v) (scan-hash ht)))
    (cons k v)))

scan-hashはキーと値の多値を返すので、それをmappingで拾っています。

分割代入
(loop :for (a b) in '((1 2) (3 4) (5 6) (8))
      :collecting (list a b 'foo))
;==> ((1 2 FOO) (3 4 FOO) (5 6 FOO) (8 NIL FOO)) 

;; SERIESで
(collect 
  (mapping ((x (scan '((1 2) (3 4) (5 6) (8)))))
    (destructuring-bind (a &optional b) x
      (list a b 'foo))))

分割代入の機構は存在するのかどうかが分からなかったので、mappingの内部でdestructuring-bindを使用しています。

要素ごとに処理
(loop for i in '(1 2 3) do (print i))
;1
;2
;3

;; SERIESで
(iterate ((x (scan '(1 2 3))))
  (print x))

mapppingとiterateは、mapcarとmapcのような関係です。ということで、副作用が目的なので、iterateを使っています。

(loop for i on '(1 2 3) do (print i))
;(1 2 3)
;(2 3)
;(3)

;; SERIESで
(iterate ((x (scan-sublists '(1 2 3))))
  (print x))
(loop for i across #(1 2 3) do (print i))
;1
;2
;3

;; SERIESで
(iterate ((i (scan #(1 2 3))))
  (print i))

リスト、ベクタ、ストリング等は普通にscanで処理できます。

数値の範囲を処理
(loop for i from 1.0 to 3.0 by 0.5 do (print i))
;==>1.0
;   1.5 
;   2.0 
;   2.5 
;   3.0 

;; SERIESで
(iterate ((i (scan-range :from 1 :upto 3 :by 0.5)))
  (print i))
(loop for i from 3 downto 1 do (print i))
;==>3
;   2
;   1

;; SERIESで
(iterate ((i (scan-range :from 3 :by -1 :above 0)))
  (print i))

;downtoもあるようなのですが、手元の環境では上手く動かなかったため、:byにマイナスの数値を指定しています。

(loop for i from 3.0 downto 1.0 by 0.5 do (print i))
;==>3.0 
;   2.5 
;   2.0 
;   1.5 
;   1.0 

;; SERIESで
(iterate ((i (scan-range :from 3 :by -0.5 :above 0.5)))
  (print i))
> (loop for i from 1 to 3 for x = (* i i) do (print x))
;==>1
;   4
;   9

;; SERIESで
(iterate ((i (scan-range :from 1 :upto 3)))
  (let ((x (* i i)))
    (print x)))

iterateのボディで普通に計算してみています。seriesを加工するという手もあるのかもしれません。

フィルタリング
(loop for i from 1 to 3 when (oddp i) collect i)
;==> (1 3)

;; SERIESで
(collect
  (choose 
   (mapping ((i (scan-range :from 1 :upto 3)))
     (when (oddp i)
       i))))
  • scan-rangeで1~3のシリーズを作成
  • mappingは節の最後に評価された値を集める(#Z(1 nil 3)のようになる。)
  • chooseでシリーズからnilのアイテムを捨てる
  • collectでリストに変換

もっと短く書く方法があるに違いないですが、とりあえず…。

まとめ

以上、まだまだシリーズの一部なのですが、独自の作法はあるもののseriesは、なかなか便利な気がします。

Common Lispの標準に取り込まれることも検討されていたらしいですが、もし取り込まれていたらまた面白い展開があったような気がします。さらに巨大化しちゃいますが…。

今後もまたシリーズで処理できる例題を探して変換してみたいと思います。

2007-10-15

StumpWM

| 23:54 | StumpWM - わだばLisperになる を含むブックマーク はてなブックマーク - StumpWM - わだばLisperになる

del.icio.usのLispタグからStumpWMの紹介のムービーが流れてきたのでチェック。

The StumpWM Experience : male : Free Download, Borrow, and Streaming : Internet Archive

StumpWMはCommon Lispで書かれたウィンドウマネージャーということで、前から興味はあったのですが、この動画を観ると思いのほか使えそうなので、インストールしてみることにしました。

最初、asdf-install可能だったので、cmuclで試してみましたが、どうやら、登録されているのは古めで、試すなら、最新の方が良さそうだったので、READMEを参照しつつ再度sbclで試してみることにしました。

以下、Ubuntu 7.04 + SBCL 1.0.10 + Git版StumpWMで試しています。

StumpWMのソースの取得

$ git clone git://git.savannah.nongnu.org/stumpwm.git

で取得します。

ASDFで導入できるようにする。

ダウンロードしたものの中にstumpwm.asdがあるので、asdf::*central-registry*に登録されているパスにリンクを張ります。

専用イメージの作成

別にイメージを作成しなくても良いとは思うのですが、起動が少し速いのでイメージを作成してみることにします。cl-ppcreとclxに依存しているので、それも読み込みます。

;; make-stumpwm-core.lisp
;;
;; cd $coredir 
;; sbcl --load make-stumpwm-core.lisp

(require :asdf)
(asdf:oos 'asdf:load-op :clx)
(asdf:oos 'asdf:load-op :cl-ppcre)
(asdf:oos 'asdf:load-op :stumpwm)
(save-lisp-and-die "stumpwm-sbcl.core" :purify t)

のようなファイルを作成し、

$ sbcl --load make-stumpwm-core.lisp

を実行して、イメージを作成しました。

ウィンドウマネージャーの起動

色々方法はあると思うのですが、自分は、最初に古いstumpwmを試してしまったということもありスクリプトを色々作って対応しました。

最新だと色々準備されている様子なので、そっちを使った方が良いとは思います。

#!/bin/sh

sbcl --core ~/cl/stumpwm-sbcl.core --load ~/cl/stumpwm-sbcl.lisp
;; STUMPWM - stumpwm-sbcl.lisp
;; ================================================================
(stumpwm:stumpwm)
(cl-user::quit)

のような、stumpwmというシェルスクリプトと起動で読み込ませるファイルを作成し、

~/.xsessionに

#!/bin/sh

#~色々環境設定記述~

$HOME/bin/stumpwm

のように記述し起動させることにしました。

カスタマイズ

環境設定は、.stumpwmrcで設定します。サンプルが付いているので、それを参照しつつ設定という感じです。

使ってみる

使ってみた感じとしては、GNU Screenのウィンドウマネージャー版みたいな感じです。

初期設定では、Control-tがプレフィックスキーになっていて、そのあとにコマンドを入力します。

C-t :で式を評価できたりします。

詳細なドキュメントはないので、サンプルの初期化ファイルと、ソースを読んで、使い方を探るという感じです。

ウィンドウマネージャーの終了方法

とりあえず、どうやって終了するのか分からないので、式評価のプロンプトに(cl-user::quit)として終了させています。

まとめ

とりあえず、Common Lispで色々できるというところと、ムービーでは結構便利そうに使っていたので、しばらく常用してみたいと思います。

2007-10-08

LTD

| 19:18 | LTD - わだばLisperになる を含むブックマーク はてなブックマーク - LTD - わだばLisperになる

なんとなく前から試してみたかったPeter Norvig氏のLTD(Common lispからDylanにコンバートするツール)がどんなものなのか試してみました。

Converting Common Lisp to Dylan

多分、暫くすると導入方法等は完全に忘れてしまうのでメモ。

インストール

概要は、サイトにドキュメントがあるので、そのまま進んで行けば導入はできるようです。

最初SBCLで試してみましたが、あまり上手く動かず、試用版のAllegroはそこそこ上手く使えるようです。Clispでも打ち死しました。

関数の定義の順番で上手くloadできなかったり、パッケージのロックとぶつかってしまったりしますが、ぶつかる個所を適当にコメントアウトしたり、定義の順番を入れ換えたりしてちょっといじってやりすごしました。

;; make-ltd-core.lisp
(load "load")
(load-ltd :compile t)
(load-ltd)
(dumplisp :name "ltd-allegro.core")
(exit)

のようなファイルを作って、codeのディレクトリに移動し、

% alisp -L ../make-ltd-image.lisp

のように実行すると、イメージができるので、

alisp -I ltd-allegro.core

のように実行するとLTD込みのAllegroが起動。

変換してみる

;; foo.lisp
(defun fib (n)
  (if (< n 2)
      n
      (+ (fib (1- n))
	 (fib (- n 2)))))

(defun tarai (x y z)
  (cond ((> x y)
	 (tarai
	  (tarai (1- x) y z)
	  (tarai (1- y) z x)
	  (tarai (1- z) x y) ))
	(t y) ))

のようなファイルを作って、

(ltd-files "foo.lisp")

とすると、

// foo.lisp
define method fib (n)
  if (n < 2) n; else fib(n - 1) + fib((n - 2)); end if;
end method fib;

define method tarai (x, y, z)
  if (x > y)
    tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y));
  else
    y;
  end if;
end method tarai;

のようにdylanに変換されたfoo.dylanが生成されます。

testディレクトリには、このツールで変換された約4万行の変換されたファイルがあり、なかなか壮観です。

…しかし、自分は上手く変換できませんでした。

色々探ってみる必要がありそうです。しかし、難しそう。

Gaucheのファイルを読み込んで、Common Lispに書き出してくれるようなツールがあると便利そうだなとしばし妄想。

2006-10-18

Google Code Search

| 23:05 | Google Code Search - わだばLisperになる を含むブックマーク はてなブックマーク - Google Code Search - わだばLisperになる

暇な感じなので、Google Code Searchで、適当にLisp的

な物を検索。

Lisp系はは大きくLispとschemeの二つに絞り込めるっぽい。

何となくdefineq lang:lispって感じで探してみる。

; The DEFINEQ function is used to define a set of Interlisp functions.
; We define DEFINEQ so that it saves the function definition on a
; property of the function called INTERLISP-DEFINITION.

(defmacro defineq (&rest forms)
  (let ((value nil))
    (do ((forms forms (cdr forms)))
	((null forms))
	(setq value (cons `(setf (get (quote ,(car (car forms)))
				      'interlisp-definition)
				 (quote ,(cadr (car forms))))
			  value))
        (format t "~A " (caar forms))
        (force-output))
    (cons 'prog (cons nil value))))

こんなのが引っ掛かりました。それなりに面白そう。

2006-08-20

UNIXでのLisp生活

| 00:42 | UNIXでのLisp生活 - わだばLisperになる を含むブックマーク はてなブックマーク - UNIXでのLisp生活 - わだばLisperになる

UNIXでの生活で非常に身近なものというか概念がフィル

ター。基本的に道具は殆どフィルター。

ここを切り崩さないと、どうしても普段の生活でLispを

使うことにならない気がします。

それで、できればCommon Lispでフィルターをばしばし

書いて行きたいんですが、どうやれば良いのかしらと。

さっぱり分からない訳です。

cat foo.txt | my-cl-filter > bar.txt

このmy-cl-filterの部分が簡単に書けないと生活に密着

したLispライフは送れない、と睨んでいるのです。

それで、こういうのが実現できるものはないかと探して

いるのですが、ちょっと調べた感じでも、なかなか難し

い世界に突入してしまう感じです。

それなりにあるみたいなんですが、総じてドキュメント

が不足していて敷居がかなり高い感が強いです。

cl-launchとか結構良さげなのですが…。

ということで、順当なところで、Gaucheで行くことにし

ました。Common Lispは対処法が見付かったら挑戦しま

す。

スクリプトの引数の処理については、

Kahua HEADのEnjoy Gauche 6. スクリプトプログラム

参考になりました。

それで、catの様なフィルターとして機能するツールの

作成方法ですが、GaucheのマニュアルのSchemeスクリプ

トを書くのcatの例を眺めてみます。

#!/usr/bin/env gosh

(define (main args)   ;entry point
  (if (null? (cdr args))
      (copy-port (current-input-port) (current-output-port))
      (for-each (lambda (file)
                  (call-with-input-file file
                    (lambda (in)
                      (copy-port in (current-output-port)))))
                (cdr args)))
  0)

多分これがスタートラインですが、のっけから分かりま

せん。

地道にということで分解。

最後に0を返してますが、これはスクリプトの終了ステー

タスのようです。


(define (main args)
  128)

の様なスクリプトを実行すれば、

$ echo $?
128

みたいな。

本体を腑分けしてゆくと、ifで引数の有無を確認し、引

数がなければ、inputとoutputポートをそのまま接続し

終了、引数があれば、それをfor-eachでファイルごとに

処理みたいな感じです。

for-eachのところの処理が入り組んでいるので、

for-eachに渡されている無名関数に名前を付けて分解し

てみるとこんな感じかなと。

(define (main args)   ;entry point
  (if (null? (cdr args))
      (copy-port (current-input-port) (current-output-port))
      (for-each cat-a-file
		(cdr args)))
  0)

;; 補助関数
(define (cat-a-file file) 
  (call-with-input-file file
    (lambda(in)
      (copy-port in (current-output-port)))))

なるほど。

しかし、馴れないと難解な世界というか、とっかかりが

なかなか難しい。