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 |

2008-04-30

FARE-MATCHER

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

今回は、先日Gauche本の勉強でも使ったFARE-MATCHERです。

これは、CLに色々なパターンマッチの構文を提供するものです。

パッケージ名fare-matcher
ClikiCLiki: fare-matcher
ASDF-INSTALL
ドキュメントCLiki: fare-matcher-docs

(asdf-install:install :fare-match)一発で可能です。

fareというつづりがどっかで見たことがあるなあ、と思っていたのですが、作者がLispマシンのまとめページ等でお馴染のFare Rideau氏でした。

なるほど、それで、fare-matcherなんですね。

使い方は、上のドキュメントに詳しいですが、matchの使い勝手は、結構良いのではないかと思いました。ecaseとcaseの関係のように、マッチしないとエラーになるematch。destructuring-bindの代わりに使えそうな、letm、これらのマクロの基本となっているifmatchがあります。

(use-package "FARE-MATCHER")

(ifmatch (list a b) '(a b)
	 (list a b) 
	 nil)
;=> (A B)

(let ((pat '(:foo :bar)))
  (match pat
    ((list a b c) (list a b c))
    ((list a b) (list a b))
    (_ "マッチしませんでした")))
;=> (:FOO :BAR)

(let ((pat '(:foo :bar :baz :quux)))
  (ematch pat
    ((list a b c) (list a b c))
    ((list a b) (list a b))))
;!>>> match-failed

(letm (list* a b c d) '(1 2 3 4 5)
  (list a b c d))
;=> (1 2 3 (4 5))

(letm (values a b) (values 1 2)
  (list a b))
;=> (1 2)

(ifmatch (and a (of-type integer *)) '(1)
	 a)

(ifmatch (and x (cons (of-type integer) *)) '(2)
	 x)
;=> (2)

(ifmatch (like-when (cons x y) (eql x y)) '(2 . 2) x)
;=> 2

(ifmatch (like-when `(,x ,@y) (eql x y)) '(2 . 2)
	 x)

(defun my-length (lst)
  (ematch lst
    (`(,* ,@tail) (1+ (my-length tail)))
    (() 0)))

(MY-LENGTH '(foo bar baz))
;=> 3

SLIME勉強会メモ

| 05:01 | SLIME勉強会メモ - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会メモ - わだばLisperになる

思い出したネタをメモ

  • CLで#'とかfunctionとか付けるのうざいというSchemerへ

(do-symbols (s)
  (when (and (fboundp s) (not (macro-function s)))
    (setf (symbol-value s) (symbol-function s))))

(mapcar identity '(foo bar baz))
=> (foo bar baz)

(apply list '(a b c d e))
=> (a b c d e)

という無駄な抵抗はどうでしょうか。

SLIME勉強会メモ

| 05:01 | SLIME勉強会メモ - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会メモ - わだばLisperになる

思い出したネタをメモ

  • CLで、マルチスレッドと大域変数の関係はどうなっているんでしょう。

→そういえば、どうなってるんだろうね。

LISP1.5でL-99 (P05 リストを逆転させる)

| 04:41 | LISP1.5でL-99 (P05 リストを逆転させる) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P05 リストを逆転させる) - わだばLisperになる

今回も再帰と繰り返しバージョンを作ってみました。

REV((FOO BAR BAZ))
REV-ITER((FOO BAR BAZ))

;  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; REV
;
; ((FOO BAR BAZ))
;
;
; END OF EVALQUOTE, VALUE IS ..
; (BAZ BAR FOO)
;
;  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; REV-ITER
;
; ((FOO BAR BAZ))
;
;
; END OF EVALQUOTE, VALUE IS ..
; (BAZ BAR FOO)

DEFINE((
(REV (LAMBDA (LST)
       (COND ((NULL LST) () )
             (T (APPEND (REV (CDR LST)) (LIST (CAR LST)))))))

(REV-ITER (LAMBDA (LST)
            (PROG (L ACC)
                  (SETQ L LST)
               L  (COND ((NULL L) (RETURN ACC)))
                  (SETQ ACC (CONS (CAR L) ACC))
                  (SETQ L (CDR L))
                  (GO L))))
))

LISP引きこもり生活 (6) はてダラの代わりにSLIME

| 03:07 | LISP引きこもり生活 (6) はてダラの代わりにSLIME - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (6) はてダラの代わりにSLIME - わだばLisperになる

CLのイメージの中に引きこもって生活する日々をつづっております。

自分はこのグループの日記は、simple-hatena-modeで書いて、hw.plは使わず、SLIMEを使ってCL経由で投稿しているのですが、content-length関係でたま怒られることがありました。

quekさんのエントリ

でcontent-lengthを指定すること、というのを読んで、そうか指定してないのが原因か!、と思い早速指定してみることにしました。

それとquekさんは、はてなだけでなくBloggerでもエントリを書かれているのですが、EmacsのMuseモードでエントリを書いて、MuseモードからSLIME経由で送信するというエントリを書かれています。

いつもプラクティカルな内容で非常に参考になります!

自分も真似して、simple-hatena-modeでポストする関数を上書きして、SLIMEに命令を送信するという方式に変更してみました。

具体的には、simple-hatena-submitを上書きして、SLIMEに実行させたい式を送るという流れになります。

自分の場合は、SLIMEは起動させっぱなしなので、コードのバッファやREPLから命令していたのですが、やっぱり日記のバッファから送信できた方が便利かなということで…。

ということで、テスト投稿!。…してみたら、内容の先頭に*edit*という印が付いてしまう現象に遭遇。うーん、何で急に*edit*とか付くようになったんだろう…。

(defun simple-hatena-internal-build-command ()
  (format "(hw:post-yyyymmdd-entry \"%s\" \"%s\" :group \"%s\")"
          (buffer-file-name)
          simple-hatena-local-current-buffer-id 
          simple-hatena-local-current-buffer-group))

(defun simple-hatena-submit ()
  "はてなダイアリー/グループに投稿する。"
  (interactive)
  (slime-repl-send-string 
   (simple-hatena-internal-build-command)))

2008-04-29

CLOS指南

| 07:11 | CLOS指南 - わだばLisperになる を含むブックマーク はてなブックマーク - CLOS指南 - わだばLisperになる

Joe Marshall氏がCLOSについて尋ねられたことをもとに書いた記事。

英語もCLOSも分からないので意味が捕捉できないのだけれど、なんとなく

(1)defclassは、defstructの強化版だと思って、構造体を使うときは、defclassを使おう。

(2)そして、defclassで定義されるアクセサを使い、with-slotsや、slot-valueの利用を避ける(with-slotsや、slot-valueは低レベルなアクセスに利用するもの)

(3)無駄に手の込んだメソッドコンビネーションを使わない。こりゃ:AROUNDメソッドが必須になるな、と思う局面でも大抵、通常のメソッドの中からcall-next-methodすれば済んだりする。

のように解釈。オブジェクトのリネームについては、内容が全然理解できなかった…。

とりあえず、defstructがでてくる局面では、defclassを使うようにすれば、CLOSを使う機会は増えるのかもしれない。

DylanでL-99 (P04 リストの長さ)

| 03:31 | DylanでL-99 (P04 リストの長さ) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P04 リストの長さ) - わだばLisperになる

Dylanでは、シンボルの大文字と小文字を区別しないみたいです。つまりCLと同じ。

ということは、好きに大文字と小文字を混在させて書いたりできるわけですね。

今回reduceを使ってみましたが、引数の順番が決まってるところが違う位でCLのreduceと緒です。

mapや、reduceとなると、無名関数を使いたくなるわけですが、Dylanでは、(lambda() ...)はmethod() ... endと書かれ、クロージャを作るのにもmethodです。

((lambda (x) (+ 3 x)) 97)

は、

method(x)
  3 + x
end(97);

といった感じになるみたいです。

うーん、知れば知るほど中間記法のCLOSという印象は強まります…。

format-out("%d\n", len(#(foo:, bar:, baz:, quux:)));
//=> 4
format-out("%d\n", "foo bar baz".len); // こういう風にも書けるらしい
format-out("%d\n", len("foo bar baz"));
//=> 11

// Code
module: l99-04

define generic LEN
    (sequence :: <sequence>)
 => (result :: <integer>);

define method LEN
    (sequence :: <sequence>)
 => (result :: <integer>)
  reduce(method(res, _) 1 + res end,
         0,
         sequence)
end method LEN;

pfcでL-99 (P04 リストの長さ)

| 00:50 | pfcでL-99 (P04 リストの長さ) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P04 リストの長さ) - わだばLisperになる

そもそもpfcの特長をあまり把握していないので、普通にscheme的なものとして書いてしまうなあ…。

pfcにはlengthが備え付けで存在しています。

(len '(1 2 3 4))
;=> 4

(def (len lst)
  (if (null lst)
      0
      (1+ (len (tl lst)))))

2008-04-28

SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (6)

| 05:04 | SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (6) - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (6) - わだばLisperになる

SLIME勉強会参加者の皆様のログです。

tsz氏

mokehehe氏

NANRI氏

ログありがとうございます!

CLで学ぶ「プログラミングGauche」 (7.3〜7.6)

| 04:36 | CLで学ぶ「プログラミングGauche」 (7.3〜7.6) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (7.3〜7.6) - わだばLisperになる

今回は、7.3からの再開です。

7.3 ローカル変数

このセクションで、lambdaとletの関係の説明がでてきます。

CLではlet、let*はlambdaの糖衣構文ではなく、スペシャルフォームです。

歴史的には、MacLISP等でもletはlambdaの糖衣構文として登場したようで、Schemeの方が伝統に忠実といったところかもしれません。

CLもレキシカルスコープなのでletとlet*の機能については、ほぼ同じです。

letrecですが、CLには、let、let*からの延長としてのletrecに正確に相当するものはなく、似たところでは、labelsでしょうか。

labelsで置き換えると

(labels ((sum (lst)
	   (cond ((null lst) 0)
		 ((numberp (car lst)) (+ (car lst) (sum (cdr lst))))
		 ('T (sum (cdr lst))))))
  (sum '(1 3 #:f 6 #:t 9)))
;=> 19

;; ローカルな相互再帰
(labels ((even? (n)
	   (cond ((zerop n) 'T)
		 ((> n 0) (odd? (1- n)))
		 ('T (odd? (1+ n)))))
	 (odd? (n)
	   (cond ((zerop n) nil)
		 ((> n 0) (even? (1- n)))
		 ('T (even? (1+ n))))))
  (even? 10))
;=> T

のようになります。

labelsは、CL以前のMacLISP系処理系には存在しませんでした。

意外にも初期のSchemeに存在していたりするのですが、CLのlabelsは、Scheme由来なのかもしれません。

Schemeではletrecに吸収されたのか、消えてしまいましたが…。

何れにせよ、どちらも大元は、LISP1.5のLABELに由来するものだとは思います。

7.4 可変長引数を取る

可変長引数については、CLとSchemeとでは違っていて、引数の性質については、ラムダリストパラメータで指定します。

MacLISP系は大体共通でEmacs Lispでも大体同じです。

ラムダリストパラメータを使わない場合は、Schemeと同じくすべて必須引数になります。

指定した場所以降を纏めてリストにして受け取りたい場合は、&restをつけます。

;; Scheme
((lambda (a . b) (list a b))
 1 2 3)
;=> (1 (2 3))

;; CL
((lambda (a &rest b) (list a b))
 1 2 3)
;=> (1 (2 3))

Scheme風にドット表記も使える局面はあることはあるのですが、ラムダリストパラメータも使えるので、トリビアとして憶えておく位かもしれません。

(destructuring-bind (a . b) '(1 2 3)
  (list a b))
;=> (1 (2 3))

defmacroの引数等でもこういう風に書くことは可能ではあります。

[練習問題]

(defun my-list (&rest args)
  args)

(MY-LIST 1 2 3 4)
;=> (1 2 3 4)

SBCLでもLISTの実装はこのまんまなコードです。

しかし、CLでは、&restで受けとる引数リストが新規に作成されることを義務づけているわけではないので、処理系によっては、このコードではまずいらしいです。*1

具体的には、applyの引数にした場合、元がリストで与えられるので問題になります。

ということは、SBCLでは、&restで受けとったパラメータは新規にコンスされたことが保証されているのでしょう。

(let ((lst '(1 2 3 4 5))
      (lst2 '(i ii iii)))
  (nconc (apply #'my-list lst) lst2)
  lst)
;=> (1 2 3 4 5)

;処理系によっては、
;=> (1 2 3 4 5 I II III)
;でも良いらしい。

X3J13では議論の結果、主にパフォーマンスの問題からこのように決定されたようです。

パフォーマンスと引き換えに、プログラマは、&restパラメータを破壊するような操作はしないように気を付ける必要があります。

とはいえ、コンピュータのパフォーマンスはどんどん上がってきているので、今どきは、&restは新規にコンスされているのが殆どのようです。

7.5 可変長引数を渡す

可変長引数を渡す際にapplyを使う、というのはCLでもリストで受け取ることになるので手法として共通です。

(defun append/log (&rest args)
  (format t "ARGS=~A~%" args)
  (apply #'append args))

(append/log '(a b c) '(1 2 3) '(7 8 9))
;>>> ARGS=((A B C) (1 2 3) (7 8 9))
;=> (A B C 1 2 3 7 8 9)

7.6 引数のパターンマッチング

lengthを使うのが不経済な場合がある(引数が何万もあったら?)ということでmatchが登場します。

Gaucheのutil.matchで採用されているのはAndrew Wright氏のmatcherらしいですが、近い感じのものとしては、FARE-MATCHERがあるようです。

FARE-MATCHERはWright氏のmatchとは書法が若干違って、パターン部がなんというか「動的な表現?」になっています。

話の流れ上、lengthが不経済な局面でmatchの実装がlengthより経済的でなければならない訳ですが、ちょっと確認してみました。

(use-package "FARE-MATCHER")

(let ((lst (loop :for i :from 0 :to 10000000 :collect i)))
  (values
   (time
    (case (length lst)
      (0 ())
      (1 'one!)
      (otherwise 'many!)))
   (time
    (match lst
      (() ())
      ((list a) 'one!)
      (_ 'many!)))))
;>>> length  0.039 seconds of real time
;>>> match  0.0 seconds of real time

要素数を10,000,000位にしないと、はっきりとした差は出ませんが、とりあえず、この局面では、FARE-MATCHERでも効率が良いようです(当たり前か(笑))

ということで、FARE-MATCHERのmatchを使うことにしてみました。

(defun append2 (a b)
  (if (consp a)
      (cons (car a) (append2 (cdr a) b))
      b))

(defun my-append (&rest args)
  (match args
    (() ())
    (`(,a) a)
    (`(,a ,@b) (append2 a (apply #'append b)))))

;; もしくは
(defun my-append (&rest args)
  (match args
    (() ())
    ((list a) a)
    ((cons a b) (append2 a (apply #'append b)))))

(MY-APPEND '(1 2 3 4) '(i ii iii iv) '(a b c d))
;=> (1 2 3 4 I II III IV A B C D)

のように書けます。

matchは便利なのでGaucheが羨しかったのですが、とりあえずfare-matcherをどしどし使って行くことにしました。

2008-04-27

SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (5)

| 23:28 | SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (5) - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (5) - わだばLisperになる

雑談篇

(1)〜(4)のような感じで、SLIMEを俯瞰してみていました。

改めて確認してみると、使っていなかった便利機能が結構ありました。

自分は、slime-selectorは全然使っていなかったのですが、使ってみたところ結構便利なので、積極的に使っていこうという感じです。

また、SLIMEとanything.elを組み合わせたりすれば、もっと便利に使えるようになりそうです。(私はanything.elを活用できていないので実際にできるのかどうか良く分かっていませんが(笑))

本編は、3時間位だったと思うのですが、そこから4時間位雑談が続きました!

以下、雑談で出たネタを纏めてみたいと思います。

実際に勉強会として集合するのは、割とエネルギーが必要、IRCで勉強会というのはどうだろう。

→まあまあ良いのでは?

日本で、IRCのLISP部屋として盛り上がっているのはどこか

→どこにもないのでは?

海外のfreenode.netの#lispは過去ログが閲覧できるプログラムが動いていて、また、HyperSpecが呼べたりlisppateというペーストしたコードを綺麗に表示してくれるサイトと連携できるようになっていて、非常に便利そう。

lisppateの機能自体は、CLのプログラムとして配布されている。

そのなかの、colorize.lispはコードをハイライト表示でHTMLにしてくれるプログラムで便利に使えそう。

lisppasteは、asdf-install可能。…でも依存パッケージがリンク切れ…。

組み込み用途などでLISPの便利さを生かせないか

ECL等、Cのコードを生成してくれる処理系を使ってみたが、なかなか思ったようなのがない。

GOOは、インラインでCのコードを書けたり、作者が組み込みに興味があるようなので、Cや組み込み分野との親和性は高そう。

http://people.csail.mit.edu/jrb/goo/

http://people.csail.mit.edu/jrb/Projects/alien-goo-intro.html

http://people.csail.mit.edu/jrb/Projects/alien-goo-talk.pdf

vcgoo154a.zipなどもあるので、Windowsでも行けるかも

ちょっと違うけれど、CをS式風に書けるのもある。

http://www.cliki.net/scexp

コードが消失していて、

http://jsnell.iki.fi/tmp/scexp-0.9.tar.gz

に置いてあるのが見付かるだけ…。

リストの破壊的操作について。ポインタ操作系。
(defun nrev (lst)
  (do ((1st (cdr lst) (if (endp 1st) 1st (cdr 1st)))
       (2nd lst 1st)
       (3rd () 2nd))
      ((atom 2nd) 3rd)
    (rplacd 2nd 3rd)))

(let ((foo '(1 2 3 4)))
  (nrev foo))
;=> (4 3 2 1)

等々、rplacaや、rplacdを駆使した分かりづらいコード。

去年ちょっとしたOn Lispブームのようなものがあった気がするが、何でなんだろうか。

On LispはCLのマクロがテーマという非常に特殊な本だと思うんだけれど…。

→やっぱり、Paul Grahamがカリスマということなのでは?

xyzzyで、defpackageでexportを指定しても、exportしてくれないのはどういう理由?

→バグな様子。

CLには、edというエディタを呼び出す関数が標準で装備されている

→edは、Lispマシンでは、Zmacsを呼び出すものだった。

→4.x BSDがメインターゲットだったFranz Lispでは、viという関数があり、viを呼び出すようになっていた。

BSDだし勿論メジャーなエディタはEMACSではなくvi。

(調べたら、exもありました。vilや、exlもあって、これは編集後ロードするというもの)

viも昔は、LISP編集に便利な機能が沢山あったらしい(あった筈)

等々他にも沢山ネタがあったと思うのですが、メモ取ってなかったので忘れてしまいました…。

まとめ

自分の突発的な見切り発車で色々ご迷惑をお掛けすることがあったと思うのですが、お付き合い頂いて本当にありがとうございました!

次回、勉強会ですが、実際に現地に集合するのは、会場の確保等、なかなか難しいところがあるので、IRCで開催してみるのはどうかチャレンジしてみたいと思います!

SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (3)

| 21:51 | SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (3) - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (3) - わだばLisperになる

CVS版 slime/contrib以下のファイルを点検してみよう

contrib以下のファイルは、どうやら(slime-setup '(...))で読みこむらしい

bridge.el

詳細不明

inferior-slime.el

inferior-lispのようなものか?

slime-asdf.el

slime-load-systemの定義らしい。

slime-autodoc.el

エコーバッファに引数情報を表示してくれる機能

読み込むと少しと反応が変わる程度かも。

slime-banner.el

起動時の最初のアニメーションとか。

slime-c-p-c.el

in-pa→in-package形式の補完が可能になる

slime-editing-commands.el

便利なものがおおいので中身を把握しておくのが吉かも。

slime-fancy.el

このファイルで便利機能をまとめてロードできるらしい

slime-fuzzy.el

曖昧方式の補完C-c C-iの方

slime-highlight-edits.el

編集したところが色付きになり、コンパイルが通るまで目立つ状態になる。

若干好き嫌いは分かれるかも

slime-indentation.el

インデントの設定を纏めたもの。

ifのインデント等は、デフォルトでは癖がある。

slime-presentation-streams.el、slime-presentations.el

追記(tszさんからのツッコミコメント)

2008/04/28 00:39
slime-presentationですが、ハッシュテーブルなどの
表示不可能オブジェクトをreplにコピーアンドペーストする
あの機能のことの様です。
さらに、replに表示された#<>記法のオブジェクトを
右クリックして調べたり出来るみたいです。

なるほど! 読み込み不可オブジェクトをREPLで評価するためには、これを読み込む必要があったんですね!

プレゼン用なのだろうか、謎。

slime-references.el

sbcl専用マニュアル。SBCLの場合は、読み込んでおくと吉かも。

デバッガに入ったときに、

See also:

SBCL Manual, Package Locks [:node]

Common Lisp Hyperspec, 11.1.2.1.2 [:section]

のような表示になり、クリックするとSBCLの該当マニュアルページに飛べる。

slime-tramp.el

ssh接続で使う際のユーティリティ。クライアントとサーバでのパス名の変換など。

slime-typeout-frame.el

主にエコーバッファを別窓で表示させるためのもの。

slime-xref-browser.el

クロスリファレンスを表示してくれる、らしい。

swank-goo.goo

gooのswankサーバ

swank-kawa.scm

kawaでもswankできるらしい。

swank-mit-scheme.scm

1. You need MIT Scheme (version 7.7.0 and 7.7.90 seem to work).

ということで、MIT Schemeでも使えるらしい。

meter.lisp

なぜここにあるのか不明。

SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (2)

| 03:32 | SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (2) - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (2) - わだばLisperになる

SLIMEを使う

便利機能紹介

ここで紹介している機能のEMACSでの基本キーバインドに関しては、tszさんが分かりやすい一覧表で纏められていますので、そちらも参照して下さい!

式の評価

slime-pprint-eval-last-expression

C-c C-p便利。これを基本に使っても良いかも。

C-c C-cでコンパイル。

コンパイルしてエラーの個所を表示してくれる。

エラーになっている個所が色付きのアンダーラインで表示される。

M-n、M-pでソースファイルの該当個所場所に飛べるので便利!

マクロ展開

非常に便利なので、本当にお勧め。これの支援が無いとマクロが書けなくなる位、便利。

SLIME上では(macroexpand ...)と書く必要はないのだ!

  • 全部展開する

C-c M-m

関数定義のソースコードに飛ぶ

M-.でソースへ

M-,でもどる

という風にどんどん進める

処理系が対応している必要あり (SBCL、Clozure等は対応している)

関数名補完

c-p-c、Fuzzy(C-c M-i)方式等いろいろある。

c-p-cは、m-v-bi→multiple-value-bindと補完。ハイフンを入力する必要あり。

Fuzzyはなんとなくの感じから展開mvbind(C-c M-i)→multiple-value-bind等々

REPLの履歴

Bashのように履歴が辿れる。

REPL上で、M-n、M-p。M-rもある。Eshellっぽいキーバインド。

読み込み不可オブジェクトをREPLで読み込める

読み込めない筈の#<foo>等もよみこめる。主に対話操作で便利。

SLIME Fancyが読み込まれている必要ありかも。

複数の処理系を切り換えて使える。

C- - M-x slimeで切り換えられる。(slime-connectでない場合)

複数の処理系を同時起動可能。しかし、編集しているLISPファイルをどの処理系が担当しているのか把握できなくなることが多いので、あまりお勧めできないかも。

パッケージの移動とソースファイルとのシンクロ

C-c ~でソースファイルとソースファイルが指定しているパッケージを同期できる。

←割とファイルを開いた段階で自動で内容をみて判別してくれるのでは?(tsz)

←そういえば、そうかもしれませんね(笑) どういう時に使うんだろう…。自分は割と手癖で打ち込んでます(g000001)

ファイルのロード、コンパイル

C-c C-lでファイルのロードC-c C-kで、コンパイルしてからロード

ASDFのロード load-systems

asdf-installはないが、 (asdf:oos ~)はSLIMEから可能。

M-x slime-load-system

と打ち込んで、パッケージを選択。パッケージ名は補完が効く。

SLIME上で(asdf:oos ...)と打つ必要はないのだ!。

HyperSpecとの連携

非常に便利で、必須な機能。(C-c C-d h)

憶えるのが厄介な、FORMATの指示子を引くこともできる! (C-c C-d ~)

Cltl2も引きたい→ILISPにcltl2.elがあるんで、引き抜いてきてロードすれば使えます。

(eval-after-load "cltl2"
  (progn 
    (require 'cltl2)
    ;; SLIMEっぽく、slime-cltl2-lookupという名前のエイリアスを作成
    (defalias 'slime-cltl2-lookup 'cltl2-lookup)
    ;;ドキュメントの場所
    (setq cltl2-root-url "http://foo.local/docs/cltl/")))
バッファ間の移動など(slime-selector活用)

M-x slime-selectorでSLIMEを操作する上で便利なslime-selectorが起動。

割と頻繁うので、

(global-set-key [(control ?\;)] 'slime-selector)

等に便利な場所に割り当てると良いかも。

slime-selectorで切り換えられるもの

キー名前説明
?Selector help buffer.ヘルプ
cSLIME connections buffer.接続状況が確認できる。接続表示の上でkを押すことにより切断可能
d*sldb* buffer for the current connection.*sbdb*のバッファ
emost recently visited emacs-lisp-mode buffer.直近のelispバッファへ移動
iinferior-lisp* buffer.*inferior-lisp* bufferへ移動
lmost recently visited lisp-mode buffer.直近のlispバッファへ移動
rSLIME Read-Eval-Print-Loop.REPLへ移動
s *slime-scratch* buffer.emacsや、xyzzyの*scratch*のような感じで、式をC-jすると後に結果の文字が挿入されるのバッファ。*scratch*が好きな人にはおすすめ。
tSLIME threads buffer.スレッド別に表示される。選択してC-kを押すことでスレッドを終了できる。
v *slime-events* buffer.クライアントとサーバの通信の様子がみれる
インスペクタ

(C-c I)でオブジェクトをいろいろ調べられる

パッケージの場合、内容を確認できるのが便利。エクスポートされている関数一覧など見れる。

SLIME Fancyが読み込まれていないと、綺麗に表示されない?

デバッガ

tで詳細表示にトグル

M-n、M-pでソースと対応づけられながら進めることが可能。

内部で式の評価をすることも可能。

クロスリファレンス

(C-c >)、(C-c <)呼んでいる関数、呼ばれている関数を調べる。一覧も表示される。

プロファイリング

slime-toggle-profile-fdefinition

TRACEのように任意の関数を指定してプロファイリング。

結果表示で結果表示。呼ばれた回数等が表示される。

全部のプロファイリングを中止。

  • slime-profiled-functions

プロファイリング中の関数の一覧を表示

全部のコッカを閉じる(過不足なく)

slime-close-all-parens-in-sexp(C-c C-])

式をコメントアウト/復帰 slime-balanced-comment

slime-insert-balanced-commentsでコメントアウト

(do ((i 0 (1+ i)))
    ((= 10 i) (p__rint "foo?")))

;; コメントアウトしたい式の先頭で、slime-insert-balanced-comments
;; →
(do ((i 0 (1+ i)))
    ((= 10 i) #|(print "foo?")|#))

;; slime-remove-balanced-commentsで復帰
;; →
(do ((i 0 (1+ i)))
    ((= 10 i) (p__rint "foo?")))
;; 楽ちんだ!

slime-remove-balanced-commentsで復帰

(define-key slime-mode-map [(control ?c) ?\;] 'slime-insert-balanced-comments)
(define-key slime-mode-map [(control ?c) (meta ?\;)] 'slime-remove-balanced-comments)

等々を.emacsへ…。

引数の補完、便利なのか微妙

C-c C-sでずらっと補完。(map __ ) → (map result-type function first-sequence more-sequences...)

ディスアセンブル

(C-c M-d)

SLIME上では(disassemble ...)と書く必要はないのだ!

トレース

(C-c C-t)で指定した関数をTRACEしたりしてくれる。SLIME上では(trace ...)と書く必要はないのだ!

関数定義の取り消し

(C-c C-u) slime-undefine-function

SLIME上では(fmakunbound ...)と書く必要はないのだ!。まあ、そこまで便利でもないけど(笑)

参考資料等

SLIMEでどういうことができるかを解説した良いムービーがある。

Marco Baringer氏がSLIMEの一通りの使い方を解説してくれる、1時間程度のムービー。

まずは、これを一回観るのが手っ取り早い?

SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (1)

| 01:37 | SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (1) - わだばLisperになる を含むブックマーク はてなブックマーク - SLIME勉強会@新宿 4/26 (土) 13:00〜20:00 (1) - わだばLisperになる

SLIMEとは

仕組み

処理系がSWANKというサーバを起動しクライアントと通信しながら開発する仕組み

EMACSが主なクライアントとして開発が進められている。

EMACS以外にもクライアントはあり、CUSP等Eclipseで動くものがある。

SWANKにはCL以外にも色々あるようで、確認できている処理系としては、CL、MIT Scheme、Scheme48、Kawa、GOO、Dylan等がある模様。

とりあえず今回は、CLの勉強会なので、CLが対象ということで進めます。

SLIMEで日本語が上手く扱えるかの問題

日本語対応はとりあえずUTF-8が無難?

UTF-8で手軽に使えるもの
  • SBCL
  • Clozure CL
  • Allegro CL
  • CLISP
駄目っぽいもの (基本的に処理系が対応していない)
  • ECL
  • CMUCL
  • ABCL
  • LispWorks(処理系自体は、UTF-8は普通に扱える。SLIMEの説明では簡単に対応できると思う、書いてはいるが、まだ未対応っぽい…。)
未確認
  • MCL

使用形態

(1)SWANKサーバを立てて、クライアントと通信する方法

メリット:

サーバは別に起動させるので、起動が速いという印象がある。(g000001)

←別にどっちの方法でもあまり変わらないのでは?(NANRI)

←そう言われれば、そうかも(g000001)

デメリット:

別にする手間が面倒臭い。

(2)EMACSで起動する時にSWANKサーバとSLIMEのクライアントを起動してしまう方法

複数の処理系を切り換えて使う場合割と切り換えがスムース(な気がする)(g000001)

SWANKを別に起動する方法の場合の設定例

別に起動する場合は、CL側で、SWANKを起動する必要がある。

処理系に初期化ファイル等を渡して起動させる方法がメジャーな様子

/usr/local/sbcl/bin/sbcl --core /var/lisp/sbcl-1.0.16-x86_64-linux.core --load ~/etc/slime.lisp

等々

文字コードをちゃんとクライアントと合わせないと接続が切れる原因になるので注意。

slime.lispの例
;;~/etc/slime.lisp
;;   (swank:create-swank-server PORT) => ACTUAL-PORT
;;   で待機開始。

;; 文字コード設定等
#+sbcl (setq sb-impl::*default-external-format* :utf-8)
#+lispworks (setq STREAM::*DEFAULT-EXTERNAL-FORMAT* '(:utf-8 :eol-style :lf))
(setq swank::*coding-system* 
      #-(or cmu ecl abcl) "utf-8-unix"
      #+(or cmu ecl abcl) "iso-latin-1-unix")

#+cmu (setf swank:*communication-style* nil)

;; swank起動
(swank:create-server :port 
                     #+sbcl  4005
                     #+clisp 4006
                     #+cmu 4007
                     #+ecl 4008
                     #+allegro 4009
                     #+lispworks 4010
                     #+openmcl 4011
                     #+abcl 4012
                     :dont-close t)

輝け! 第1回 突発性CL勉強会@新宿 銀座ルノアール 4/26 (土) 13:00〜20:00 (0)

| 01:37 | 輝け! 第1回 突発性CL勉強会@新宿 銀座ルノアール 4/26 (土) 13:00〜20:00 (0) - わだばLisperになる を含むブックマーク はてなブックマーク - 輝け! 第1回 突発性CL勉強会@新宿 銀座ルノアール 4/26 (土) 13:00〜20:00 (0) - わだばLisperになる

本日は、先週突然全くのノープランで呼び掛けたCLに関するなんらかの勉強会を開催させて頂きました!

参加者は、いいだしっぺの私g000001と勉強熱心な熱きmokehehe氏、NANRI氏、tsz氏(アルファベット順)の3名様、合わせて4名。また、今回残念ながら時間や条件等の都合が合わなかった、佐野さん、ぬえさん、参加希望ありがとうございました!

それで、テーマですが、なんとなくの流れでSLIMEに決まりました。

開催場所ですが、適当にだらだらできそうという、私の漠然とした印象とノートPCの電源等が確保できるらしい、ということでルノアールがなんとなく選ばれました。

しかし、現地に集合し、お目あてのルノアールを訪れたところ、先週から改装工事!。ということで、いきなり出鼻が挫かれました…。

とりあえず、たまたま近くにルノアールがあったので、そちらに移動。

こちらは、若干落ち着けない感じで、ノートPCもテーブルに置くスペースもないし、電源もないみたい、という感じでしたが、なんとなく見切り発車で勉強会開始。

内容としては、私が言いだしっぺということもあり、私が勝手にSLIMEの機能を私が知ってる限り列挙したネタ元を作成し、それにツッコミを入れるという感じで進行。

以下、ログと準備したネタをつらつら書いて行きます。長いので何回かに分けます。

2008-04-25

CLOSでL-99 (P19 指定した位置でローテーション)

| 12:21 | CLOSでL-99 (P19 指定した位置でローテーション) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSでL-99 (P19 指定した位置でローテーション) - わだばLisperになる

色々な型に対応する、という方向に趣旨を変更したので、ずるいけど既存の関数は普通に使うことにした。

ポジション指定が0の場合は、元の配列をコピーして返すようにしたけれど、コピーしないで返した場合、破壊的な関数の部類になってしまうんだろうか。

この関数自体は破壊していないけれど、次に来る関数が破壊的操作を加えるものだとすると、なんとなくそういう気はする…。

(rotate '(a b c d e f g h) 3)
;=> (D E F G H A B C)
(rotate '(a b c d e f g h) -2)
;=> (G H A B C D E F)
(rotate #(a b c d e f g h) 3)
;=> #(D E F G H A B C)
(rotate #(a b c d e f g h) -2)
;=> #(G H A B C D E F)
(rotate "abcdefgh" 3)
;=> "defghabc"
(rotate "abcdefgh" -2)
;=> "ghabcdef"

(defgeneric ROTATE (sequence position)
  (:documentation "Rotate a list N places to the left."))

(defmethod ROTATE ((sequence sequence) (position integer))
  (let ((position (if (minusp position)
                      (+ position (length sequence))
                      position)))
    (concatenate (class-of sequence)
                 (subseq sequence position)
                 (subseq sequence 0 position))))

(defmethod ROTATE ((sequence sequence) (position (eql 0)))
  (copy-seq sequence))

2008-04-24

Getting Started in *LISP (6)

| 23:37 | Getting Started in *LISP (6) - わだばLisperになる を含むブックマーク はてなブックマーク - Getting Started in *LISP (6) - わだばLisperになる

*LISPのチュートリアルを細々とさらっております。今回は、1.2.5章から再開

1.2.5 Personalize Your System

このセクションは作成したプログラムファイルのロードについて等の記述に軽く触れている程度の内容です。

1.3 Exiting From *LISP

*LISPはコネクションマシンに接続されたホストから操作するのですが、ホスト機としては、SUNのUNIXワークステーションや、SymbolicsのLispマシンが対応していました。

ホスト機のLispの処理系は、Lucid Common Lisp、Symbolicsは、Symbolics Common Lisp(だったのかな?)のようです。

面白いのが、Symbolicsの方は、Lispマシンということもあって、終了の方法は、パッケージを移動するだけ、というところ。

UNIXは、最近のCL処理系と同じく(quit)とかしてシェルに抜けます。

次回、第2章から再開で、パラレル変数の扱い等々です。

GOOでL-99 (P13 ランレングス圧縮 その3)

| 06:43 | GOOでL-99 (P13 ランレングス圧縮 その3) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P13 ランレングス圧縮 その3) - わだばLisperになる

WikipediaでDylanの歴史の項目を眺めていたら、GOOの作者であるJonathan Bachrach氏は、Dylanの一番最初のフリーの実装を作った人だったらしく、GOOがDylanから影響を受けているというもの宜なるかな。

supはCLのcall-next-methodのようなもので、与えた引数に次に特定できるクラスのメソッドを適用します。

(encode-direct '(a a a a b c c a a d e e e e))
;=>  ((4 a) b (2 c) (2 a) d (4 e))
(encode-direct #[a a a a b c c a a d e e e e])
;=> #[(4 a) b (2 c) (2 a) d (4 e)]
(encode-direct #(a a a a b c c a a d e e e e))
;=> #((4 a) b (2 c) (2 a) d (4 e))
(encode-direct "aaaabccaadeeee")
;=> "4;a,b,2;c,2;a,d,4;e"

(dg encode-direct (u|<seq> => <seq>))

(dm encode-direct (u|<seq> => <seq>)
  (if (empty? u)
      u
      (let ((cnt 0)
            (prev (elt u 0))
            (res (packer-fab <lst>)))
        (for ((x `(,@(as <lst> u) ,(gensym))))
            (if (= prev x)
                (incf cnt)
                (seq 
                 (pack-in res (if (= 1 cnt) prev `(,cnt ,prev)))
                 (set cnt 1)
                 (set prev x))))
        (as (class-of u) (packed res)))))

(dm encode-direct (u|<str> => <str>)
  (join (map (fun (x) 
               (if (cons? x)
                   (let (((tup num item) x))
                     (cat (to-str num) ";" (to-str item)))
                   (to-str x)))
             (sup (as <lst> u)))
        ","))

(df cons? (u|<any> => <log>)
  (and (subtype? (class-of u) <lst>)
       (not (nul? u))))

LISP引きこもり生活 (5) なんでもCL-USERに取り込めの巻

| 02:28 | LISP引きこもり生活 (5) なんでもCL-USERに取り込めの巻 - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (5) なんでもCL-USERに取り込めの巻 - わだばLisperになる

前回から2ヶ月ぶり位になってしまいましたが、「LISP引きこもり生活」はこれから続きものということで毎週水曜日に書こうと思っています!

「LISP引きこもり生活」はCLのイメージからできるだけ外出しないようにする試みです。

前回までは、お世話になったzshなどを放棄し、Lispマシン風にSLIMEからなんでも操作しよう!という流れでした。

この2ヶ月で変ったことといえば、シェル操作(OS操作)でzsh以上の便利さがなかなか実現できず、結局段々zsh様を使うことが増えて来ました(笑)

これではいけないな。CLASHとか導入してみようかな…。

それと、生活用にHOMEパッケージというものを定義してみたのですが、割と中途半端な存在で、これだったら、CL-USERにがんがん自作ユーティリティをつっこんでしまっても良いのではないかと思い、なんでもCL-USERに取り込んで、LISPイメージをダンプするようにしました。

ASDFや、ASDF-INSTALLも、USE-PACKAGEしていますが、ぶつかるものもないので普段の生活にはこっちの方が便利かもしれません。

もともとCL-USERはユーザの為の作業空間な気がするので、少々汚なくなっても良いかなと。

もちろんCLパッケージからエクスポートされている関数の名前をつけかえるようなことはまずいですが…。

なんにしろこういうカスタムイメージは作業するのには非常に便利かなと思います。

LISP1.5でL-99 (P04 リストの長さ)

| 01:37 | LISP1.5でL-99 (P04 リストの長さ) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P04 リストの長さ) - わだばLisperになる

LISP1.5には標準でLENGTHがあるようです。(エミュレータには何故か無いのですが…。)

SIZEは、再帰版で、SIZE-ITERは、ループ版ってことで書いてみました。ちなみに、DEFINEのは一度に複数の定義が書けます。

SIZE((A B C D E F))
SIZE-ITER((A B C D E F))

;  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; SIZE
;
; ((A B C D E F))
;
;
; END OF EVALQUOTE, VALUE IS ..
; 6
;
;  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; SIZE-ITER
;
; ((A B C D E F))
;
;
; END OF EVALQUOTE, VALUE IS ..
; 6

DEFINE ((
(SIZE (LAMBDA (LST)
        (COND ((NULL LST) 0)
              (T (ADD1 (SIZE (CDR LST)))))))

(SIZE-ITER (LAMBDA (LST)
             (PROG (L CNT)
                   (SETQ CNT 0)
                   (SETQ L LST)
                L  (COND ((NULL L) (RETURN CNT)))
                   (SETQ CNT (ADD1 CNT))
                   (SETQ L (CDR L))
                   (GO L))))    
    ))

2008-04-22

ACL-COMPAT

| 22:47 | ACL-COMPAT - わだばLisperになる を含むブックマーク はてなブックマーク - ACL-COMPAT - わだばLisperになる

毎週火曜は適当にCLのライブラリを紹介することにしてみました。

今回は、ACL-COMPATですが、これは、Flanz社のAllegro CLとのコンパチビリティレイヤを他の処理系にも提供するものです。

元々Allegro CL用に書かれたものが他の処理系に移植される際にこのパッケージが使われることが多いようです。

割と依存しているパッケージが多いので知らない間にインストールされているかもしれません。

パッケージ名ACL-COMPAT
ClikiCLiki: ACL-COMPAT
ASDF-INSTALL

インストール

(asdf-install:install :acl-compat)一発で可能です。

パッケージが提供するもの

  • マルチプロセッシング
  • ソケット
  • ACLのEXCLや、NET.URI、SYSパッケージで定義されている一部ユーティリティ

等々、Allegro風に見せる層を被せるのでAllegroに馴れた人は便利に使える、という感じでしょうか。

自分は、if*の印象しかなかったのですが、改めて眺めてみると結構色々あるなと思いました。

DylanでL-99 (P03 K番目の要素)

| 21:40 | DylanでL-99 (P03 K番目の要素) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P03 K番目の要素) - わだばLisperになる

DylanでCLのcondに相当するものは、caseで、CLのcaseに相当するものは、selectになります。使い勝手も大体同じ。というか、途中からS式からAlgol表記へ乗り換えた経緯もあり、LISP系でお馴染のものは大体あります。CLをAlgol風に表記しても、多分Dylanみたいになるのでしょう…。

自分は、C/Algol系の言語は殆ど書いたことがなくて、S式系の言語ばかりやっているのですが、Algol記法では

  1. セミコロンの使いどころが把握できない。
  2. リストの要素を一々コンマで区切らないといけないのがしんどい、というかコンマを忘れても気付けない。
  3. 括弧で囲まれていると前置記法として読み書きしてしまうので、if (= x y)等がエラーになってもずっと発見できない。
  4. 括弧の使いどころが分からない。(foo 1 2 3)は、foo(1, 2, 3)と書かれるんだと思いますが、foo 1 2 3としてしまう。

という感じで、良くC/Algol系の人がLISPで遭遇する困難と正反対になっている気がしないでもありません。

そして、この辺の問題は気付けないってのが、非常にストレスに感じます。

この辺は、馴れなんじゃないかなと思いますが、自分のように最初からLISPの割合が高い人間が正反対の性質を持つということは、もしかしたら、「どっちににも馴れる」というのは比較的難しいところで、「どっちか」で落着いてしまうところなのかもしれません。

もちろん、ピアノもギターも弾ける人はいる訳で、訓練次第だとは思いますが、多分、楽器の乗換え位しんどい気がします。

element-at(#(a:, b:, c:, d:), 3)
//=> #"c"

;; Code
module: l99-03

define generic element-at 
    (sequence :: <sequence>, position :: <integer>)
 => (result :: false-or(<symbol>));

define method element-at 
    (sequence :: <list>, position :: <integer>)
 => (result :: false-or(<symbol>))
  case 
    empty?(sequence) => #f;
    1 >= position => head(sequence);
    otherwise => element-at(tail(sequence), position - 1);
  end case
end method element-at;

pfcでL-99 (P03 K番目の要素)

| 02:48 | pfcでL-99 (P03 K番目の要素) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P03 K番目の要素) - わだばLisperになる

pfcには、condがないようなので、ifの組み合わせで書いてみました。

;(element-at '(a b c d e) 100)
;=> c

(def (element-at lst k)
  (if (null lst) 
      ()
      (if (>= 1 k)
          (hd lst)
          (element-at (tl lst) (1- k)))))

2008-04-21

funcall

| 20:30 | funcall - わだばLisperになる を含むブックマーク はてなブックマーク - funcall - わだばLisperになる

昨日、funcallを!にして目立たなくするようなリーダーマクロを考えてみた、とか書いておいて全く一貫性がないけれど、自分は、funcallは全く気にならないタイプ。

気になる人と、気にならない人がいると思うけれど、もしかしたら、気にならない見方をしているからかなと、ふと思ったのだった。

自分は、(= 3 foo)とか、(+ 2 foo)のような、書法が好きで、文の前に重心を置きたくなってしまう。

一般的には、(= foo 3)、(+ foo 2)の方が落着く人が多いんだと思う。

何で、(+ 2 foo)のような書き方が好きなのかというと、+ 2まで関数として考えた方が分かり易いんじゃないかなあと思うからで、pfcや、Qiのようにカリー化が簡単に表記できる方言だと、実際((+ 2) foo)と書けるし、書いてても気持ち良い。でも目に対する可読性は下がるかもしれない。でも脳内の可読性は別に下がらないと思う。

それでfuncallなんだけども、funcallも多分、((funcall #'+) 3 3)と見えてるか、考えているのであまり気にならない気がする。

ただ、実際CLでカリー化構文が可能になったとしても、(funcall (funcall #'+) 3 3)ということになるのかもしれないので、そこは、自分に都合良く解釈している。

ただ、この辺は、エディタの表示で隠蔽できるところだと思う。(funcall foo 3)→(foo 3)とかできそう。

CLで学ぶ「プログラミングGauche」 (7.2)

| 01:48 | CLで学ぶ「プログラミングGauche」 (7.2) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (7.2) - わだばLisperになる

7.2 手続きを取る手続き

前回からの続きです。7章は割と長いので小分けにしててくてく進んで行きます。

このセクションは高階関数のセクションで、かなり良いことが書いてあると思うので、個人的には何回も復習して,こういった種類の抽象化ができるようになりたいところです。

なぜ関数プログラミングは重要か」という有名な論文がありますが、このセクションもまた関数プログラムのエッセンスを濃縮して教えてくれているように思います。

とりあえず、for-eachの解説からなのですが、CLだと、mapcがfor-eachに相当します。

共通点としては、全体の返り値はあてにしないで、ループ構文として副作用目的で使われる、ということです。

そのため、for-each返り値は未定義であり、それは期待して使われないものということになっています。

mapcもCLtL2にはスタイルとして副作用目的で使われる、とあります。ちなみに、mapcの返り値は、未定義ではなく第2引数となっています。(つまり1番目のリスト)

また、昔(MacLISPの頃)は、mapcは好んで使われていたようですが、dolistが登場してからは、dolistが人気のようで、あまり使われなくなって来ています。

ここでは、とりあえず形が似ているということと、dolistはマクロなので高階関数の引数にするには都合が悪いので、mapcでfor-eachの代用をさせます。

また、schemeのmapは、CLのmapcarに相当しますが、R5RS的には引数の評価順序が規定されていないそうで、規定されたものには、srfi-1のmap-in-orderがあるようです。Gaucheのmapは、map-in-orderになっているとのことです。CLのmapcarは(というか基本的にどの関数でも)頭から順に評価されます。

色々CLに翻訳

(defun tree-walk (walker proc tree)
  (funcall walker (lambda (elt)
                    (if (listp elt)
                        (tree-walk walker proc elt)
                        (funcall proc elt)))
           tree))

(tree-walk #'mapc #'print '((1 2 3) 4 5 (6 (7 8))))
;=> 1 2 3 4 5 6 7 8

(defun reverse-for-each (proc lst)
  (mapc proc (reverse lst)))

(tree-walk #'reverse-for-each #'print '((1 2 3) 4 5 (6 (7 8))))
;>>> 1 2 3 4 5 6 7 8

(tree-walk #'mapcar (lambda (x) (* x 2))
           '((1 2 3) 4 5 (6 (7 8))))
;=> ((2 4 6) 8 10 (12 (14 16)))

(defun reverse-map (proc lst)
  (mapcar proc (reverse lst)))

(tree-walk #'reverse-map (lambda (x) x)
           '((1 2 3) 4 5 (6 (7 8))))
;=> (((8 7) 6) 5 4 (3 2 1))

(defun reversed (walker)
  (lambda (proc lst)
    (funcall walker proc (reverse lst))))

(funcall (reversed #'mapcar) #'values '(1 2 3 4))
;=> (4 3 2 1)

(tree-walk (reversed #'mapcar) (lambda (x) x)
           '((1 2 3) 4 5 (6 (7 8))))
;=> (((8 7) 6) 5 4 (3 2 1))

という流れで、ステップを踏んでどんどん骨組みを括り出す方法が解説されているのですが、非常にためになります!

[練習問題]

;; 6章の練習問題やってなかった…。
(defun filter (pred lst)
  (reduce (lambda (x res)
            (if (funcall pred x)
                (cons x res)
                res))
          lst :initial-value () :from-end 'T))

(defun for-each-numbers (proc lst)
  (mapc proc (filter #'numberp lst)))

(for-each-numbers #'print '(1 2 #:f 3 4 #:t))
;>>> 1 2 3 4

(defun map-numbers (proc lst)
  (mapcar proc (filter #'numberp lst)))

(map-numbers #'values '(1 2 #:f 3 4 #:t))
;=> '(1 2 3 4)

(defun numbers-only (walker)
  (lambda (proc lst)
    (funcall walker (lambda (x) 
                      (and (numberp x)
                           (funcall proc x)))
             (filter #'numberp lst))))) 

(funcall (numbers-only #'mapcar) #'values '(1 2 #:f 3 4 #:t))
;=> (1 2 3 4)

(funcall (numbers-only #'mapc) #'print '(1 2 #:f 3 4 #:t))
;>>> 1 2 3 4

(defun numbers-only-for-tree (walker)
  (lambda (proc tree)
    (funcall walker proc
	     (filter (lambda (x) (or (listp x) (numberp x)))
		     tree))))

(tree-walk (numbers-only-for-tree #'mapc) #'print 
           '((1 2 3 #:t) 4 5 (6 (7 8 #:t ((((((#:f 9))))))))))
;>>>1 2 3 4 5 6 7 8 9

(tree-walk (numbers-only-for-tree #'mapcar) #'values 
 '((1 2 3 #:t) 4 5 (6 (7 8 #:t ((((((#:f 9))))))))))
;=> ((1 2 3) 4 5 (6 (7 8 ((((((9)))))))))

;; おまけ
;; funcallが目に痛いという場合、リーダーマクロで!にしてしまうのはどうか
;; という試み。その代わり、!が名前に使い辛くなるという諸刃の剣。
(set-macro-character #\! (lambda (str char)
                           (declare (ignore str char))
                           'funcall))

(defun numbers-only-for-tree (walker)
  (lambda (proc tree)
    (!walker proc
       (filter (lambda (x) (or (listp x) (numberp x)))
		       tree))))

; と書ける。
;; 追記/numbers-only-for-treeの解釈を激しく間違っていたので修正しました(^^; 2008/5/15

2008-04-20

CLで学ぶ「プログラミングGauche」 (7)

| 14:39 | CLで学ぶ「プログラミングGauche」 (7) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (7) - わだばLisperになる

毎週日曜日にプログラミングGaucheをさらってみることにしました。

今回は、「7章 手続き」です。長いので、セクションごとに分割します。

7.1 手続きオブジェクト

CLは、Schemeとちがって名前空間が1つではありません。

とりあえず、sum-of-numbersを評価すると、sum-of-numbersの変数としての中身が出てきます。

defunで関数を定義するとシンボル(の関数の場所)に関数が登録されるので、シンボルに関連付けられた関数を取り出すには、

(function sum-of-numbers)

;; 略記
#'sum-of-numbers

;; symbol-functionを使った方法
(symbol-function 'sum-of-numbers)

とする必要があります。

こうすると、Gaucheのように

#<FUNCTION SUM-OF-NUMBERS>

のような結果が返って来ます。

CLでの#< 〜 >の定義ですが、#<というのは、読み戻した場合にはエラーにするための構文です。

割と、#< 〜 >の出力を読み戻して使いたいけど、できないんですが…というのはFAQっぽいですが、#<で囲まれているということは、わざわざ読み戻させないように書いているということなので、できない、と考えても良いかもしれません。(ちなみにLispマシンや、SLIMEの会話的操作では可能です。恐らく会話的操作だとできた方が便利だからではないでしょうか。)

別の名前を付ける
(define x sum-of-numbers)

に相当するのは、

(setf (symbol-function 'x) #'sum-of-numbers)

となるかと思います。Schemeと同様にsum-of-numbersが別の内容に定義されてもxは元の定義のままです。ただし、これはシンボルに関連付けられているので、defineと違ってどこで定義しようが大域的になります。ローカルにしたい場合は、letに変数として格納し、funcallを付けて呼び出す等々…になるので、別の名前を付けるという視点ではローカルの場合defineのようにはいかないようです。

無名関数について

CLの場合も同じく無名関数はlambdaで作れます。

ここでdefineについて説明がありますが、defunは関数オブジェクトと名前を関連付ける以外にもドキュメント等色々と関連付けることができ、その他拡張でソースコードの場所を記録したりもします。これはもちろん、1つのシンボルが沢山の情報を属性として分けて格納することができるためです。

無名関数の使われる場面
(defun len (lst)
  (flet ((increment2 (a b)
           (declare (ignore b))
           (1+ a)))
    (reduce #'increment2 lst :initial-value 0)))

;; 無名関数で
(defun len (lst)
  (reduce (lambda (a b) (declare (ignore b))
           (1+ a))
          lst :initial-value 0))

(defun max-number (lst)
  (if (endp lst)
      (error "max-number needs at least one number")
      (reduce (lambda (a b) (if (> a b) a b))
              (cdr lst) :initial-value (car lst))))

(defun print-elements (lst)
  (reduce (lambda (a b) (declare (ignore a)) (print b)) lst
          :initial-value nil))

ここでは無名関数の使われ方が説明されています。CLにはfoldがないので、reduceを使ってみましたが、処理する関数が取る引数の順番が逆なのでややこしいです。srfi-1には、foldのリストが1つだけとれるバージョンのreduceという名前の関数もありますが、これもCLとは逆です。

また、(declare (ignore 〜))が加わっていますが、これはコンパイラに使われない引数を無視するように指示するものです。無くても大丈夫ですが、SBCLだと未使用変数が警告になるので、自分は付けるようにしています。面倒ですがバグが入りにくくなるメリットもあったりはします。

2008-04-19

FORMAT指示子を自作関数で理解する試み

| 21:51 | FORMAT指示子を自作関数で理解する試み - わだばLisperになる を含むブックマーク はてなブックマーク - FORMAT指示子を自作関数で理解する試み - わだばLisperになる

FORMAT関数は便利なのですが、指示子の引数の扱いが全然暗記できないので、せめて良く使う~Aだけでもマスターしたいと思い記憶できるような方法をあれこれ考えてみました。

まず、~Aには、~Aと~@Aがあるのですが、日本語版のCLtL2では「文字列は右に詰められる(もし@修飾子が用いられるのであれば左に)」という記述です。

これだと、~Aは右詰めかと思うのですが、原文は、The string is padded on the right (or on the left if the @ modifier is used) with at least minpad copies of padchar.とのことで、右側がパディング文字で埋められた文字列、つまり、結果としては、日本語でいう左詰めになるというややこしさ。

面倒なので~Aは左詰め、~@Aで逆転、とおぼえることにします。

とりあえず、@の役目は暗記できたので、次に引数です。

説明を読んでも理解できないので、~Aと同じ動作をする関数を作ってみました。

(defun A (s arg &rest params)
  (destructuring-bind
        (&optional (mincol 0) (colinc 1) (minpad 0) (padchar " ")) params
    (let ((pad (max 0 minpad (- mincol (length arg)))))
      (princ arg s)
      (loop :repeat (* pad colinc) :do (princ padchar s)))))

(format t "~@{~10,1,10,'*A~}" "foo" "bar" "baz")
;>>> foo**********bar**********baz**********

(format t (lambda (s &rest args)
            (dolist (x args) ; ~@{ ... ~}
              (A s x 10 1 10 "*")))
        "foo" "bar" "baz")
;>>> foo**********bar**********baz**********

CLtL2の内容をコードに起したつもりではあるのですが、もしかしたら間違ってるかもしれません。

まあ、でも各引数の意味は理解できました。

それで、引数は省略できるのですが、省略にも順序があって、

;; 4つ全部
(format t "~99,88,77,'*A" "foo")

;; 3つの場合
(format t "~99,88,77A" "foo")(format t "~99,88,77,' A" "foo")

;; 2つの場合
(format t "~99,88A" "foo")(format t "~99,88,0,' A" "foo")

;; 1つの場合
(format t "~99A" "foo")(format t "~99,1,0,' A" "foo")

;; 0
(format t "~A" "foo")(format t "~0,1,0,' A" "foo")

という右から順番に省略されたことになります。自作の関数も&restと&optionalの組み合わせで再現してみました。

それで、引数の順番ですが、ここは丸暗記です。

mincol、colinc、minpad、padcharという並びですが、min-col-inc、min-pad-charと結合して無理に憶えたいと思います。ちなみに、~Dでは、また別の並び方をしています…。

まだまだ複雑怪奇な指示子は沢山ありますが、自作してひとつずつ理解していくというのも、まあまあ、アリかなと…。

Arcの述語のネーミングルール

| 19:03 | Arcの述語のネーミングルール - わだばLisperになる を含むブックマーク はてなブックマーク - Arcの述語のネーミングルール - わだばLisperになる

古典的なLISPだと-P、Schemeだと-?を末尾に付けると述語になるというのが何となくの慣習。

Arcだと、a-という風に頭に、aがつくのが多い。acons、alist等。

symmetricという場合を考えてみるにsymmetricp、symmetric?はOKだけど、asymmetricでは非対称という正反対の単語になってしまう。

こういう場合はどうしたら良いのだろう。

ArcでL-99 (P56 二分木が線対称な構成かを調べる)

| 18:59 | ArcでL-99 (P56 二分木が線対称な構成かを調べる) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P56 二分木が線対称な構成かを調べる) - わだばLisperになる

バックトラックをどうしようか、と考えていたら全然進めなくなったので、それは置いておいて、まずは普通にリスト操作で解いて後で考えることにしました。

後ではやらない可能性もありますが…(笑)

mirrorという補助関数を定義して解いてみよう、ということなので、反転して同じ構成かを比較しろ、ということなのかと思い、そういう風に書いてみました。

個々の葉の要素が同じかではなく、構成が同じかどうか、ということなので、skeltonという構成をコピーする関数を定義して比較しています。

(symmetric? '(x nil (x (x (x nil nil) (x nil nil))
                       (x nil (x nil nil)))))
;=> nil

(symmetric? '(x (x (x (x nil nil) (x nil nil))
                   (x nil (x nil nil)))
                (x (x (x nil nil) nil)
                   (x (x nil nil) (x nil nil)))))
;=> t

(def mirror (tree)
  (if no.tree
      ()
      (let (rt l r) tree
        `(,rt ,mirror.r ,mirror.l))))

(def skelton (tree)
  (if no.tree
      ()
      (let (rt l r) tree
        `(x ,(skelton l) ,(skelton r)))))

(def symmetric? (tree)
  (let skel (skelton tree)
    (iso skel (mirror skel))))

2008-04-18

DylanでSwank

| 23:50 | DylanでSwank - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでSwank - わだばLisperになる

DylanでSWANKサーバ立ててSLIMEからDylanを使うというdswankなるものを発見したので、駄目もとで挑戦の巻

とりあえず、MacOSXで挑戦したのですが、駄目でした。

しょうがないので、Linuxで挑戦したのですが、64bit UbuntuではDylan自体が上手く動かせないので、qemu(kvm)でDebianを起動してビルドしたところ成功しました。

ビルドには、dswank以外に、lisp-readerが必要なようです。

ソースは、404 Not Foundで確認できます。

1. ソースをsvnで引っ張ってくる。

$ svn co svn://anonsvn.gwydiondylan.org/scm/svn/dylan/trunk/fundev/sources/lib/lisp-reader
$ svn co svn://anonsvn.gwydiondylan.org/scm/svn/dylan/trunk/fundev/sources/lib/dswank

2. ビルド

上記二つを同じディレクトリに設置したします。opendylanを適切に起動できるように設定したとして、

$ opendylan -build dswank.hdp
...
Hacker Edition
Version 1.0 [Beta 4]
Copyright (c) 1997-2004, Functional Objects, Inc.
Portions Copyright (c) 2004-2007, Dylan Hackers
All rights reserved.

Project file for missing 'lisp-reader':

となるので、何を指定して良いのか良く分かっていませんが、lisp-readerのファイルを指定

../lisp-reader/lisp-reader.hdp
....
...

ゴリゴリビルドが始まります。30分位ビルドに時間が掛りました。

Linux版の場合、

3. 実行してみる

~/Open-Dylan/bin/dswankにバイナリができるので実行。

起動してはいる様子ですが、外部から接続もできず。どういうことなのかと思い、ソースを眺めてみると、--listenという引数が取れる模様なので、--listenを付けてみると、

$ ~/Open-Dylan/bin/dswank --listen
Waiting for connection on port 4005

となり、

ポート4005番で待機しているようです。

ということで、SLIMEから接続してみます。

M-x slime-connect
127.0.0.1
4005

→接続できました!

まだ、使い方が良く分からないのですが、プロジェクトをオープンしたりビルドコマンドを実行したりはできるようです。

CLのSLIMEと比べてしまうと、かわいそうですが、現状では、ちょっと便利なDylanシェル位の感じです。

とりあえず記念にスクリーンショットを取ってみました。

Ubuntu 7.10の上のqemu上のDebianでdswankを起動して、MacOSXにポートフォワードしてSLIMEと接続という、意味なくややこしい構成になっています。

全然関係ないですが、Dylan用の設定がないか、CVS版のSLIMEを改めて確認してみたら、swank-goo.gooというものを発見しました。GOOでもSWANKが起動できるのか!!

ということで、次はGOOで挑戦します!

CLOSでL-99 (P18 範囲切り出し)

| 18:02 | CLOSでL-99 (P18 範囲切り出し) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSでL-99 (P18 範囲切り出し) - わだばLisperになる

前回までは、CLOSの型でディスパッチする機能にIF式の代わりをさせるという妙なことをしていましたが、大体パターンが分かったのと、これ以降はかなり複雑になるということで、普通にリストだけでなく色んなオブジェクトに対応することでCLOSらしさを出して行くことにしました。しかし、GOOとかDylanと被るんだなあ…。

今回のお題は、範囲の切り出しで、標準の関数だと、subseqがあります。

リストの処理は普通ですが、ベクタは、普通版と、共通部分をaroundメソッドで括り出す書き方をしてみました。

中間処理は、リストを使い、最後に入力の型に合せて型変換する、という流れです。

aroundはこういう使い方もするのかなあ、と手元のソースを調べてみたのですが、こういう風にはあまり書かないようです(笑)

AOP系のソースにはちらっとありましたが、どうなんでしょうねえ。

確かにメソッド結合を濫用していると、あっという間に把握できないコードになるというのは分かる気がします…。

(slice "abcdefghijk" 3 7)
;=> "cdefg"
(slice #(a b c d e f g h i j k) 3 7)
;=> #(c d e f g)
(slice '(a b c d e f g h i j k) 3 7)
;=> (c d e f g)

(defgeneric slice (lst start end)
  (:documentation
   "Given two indices, I and K, the slice is the list containing
the elements between the I'th and K'th element of the
original list (both limits included). Start counting the
elements with 1."))

(defmethod slice ((sequence list) (start integer) (end integer))
  (loop :for idx := 1 :then (1+ idx)
        :for x :in sequence :when (<= start idx end) :collect x))

;; 1 普通に
(defmethod slice ((sequence array) (start integer) (end integer))
  (loop :for idx := 1 :then (1+ idx)
        :for x :across sequence :when (<= start idx end) :collect x :into res
        :finally (return (coerce res (class-of sequence)))))

;; 2 共通部分をaroundメソッドに分割したバージョン
(defmethod slice ((sequence array) (start integer) (end integer))
  (loop :for idx := 1 :then (1+ idx)
        :for x :across sequence :when (<= start idx end) :collect x))

(defmethod slice :around ((sequence sequence) (start integer) (end integer))
  (coerce (call-next-method) (class-of sequence)))

CLOSで返り値の型チェックはできたりするのか

| 12:20 | CLOSで返り値の型チェックはできたりするのか - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSで返り値の型チェックはできたりするのか - わだばLisperになる

やりたいこと:

Dylanや、GOOのように総称関数で全体の返り値の型を指定してチェックしたい。

考えてみたこと/試してみたこと:

返り値の型をチェックするのに共通のaroundメソッドを定義すれば良いのかなと思い、試してみる。

感想:

一応できてはいるけど…

  1. どっちにしろ動的に基本メソッドを実行した後にチェックするので微妙。
  2. あんまり綺麗に書けてないし、これでは逆にバグの元になりそう。
  3. コンパイラが型の不整合を教えてくれることも期待できないのかも。
  4. defgenericで宣言したい。

マクロにすれば良いんだろうけど、こういう場合の定番の書法があれば、定番書法が知りたい。きっとDylan風にしたい、って人は過去にいた筈。

MOPは良く分からないけど、こういうのはMOPの領域に踏み込む必要があるんだろうか…。

;; あまり役に立っていない微妙なdefgeneric
(defgeneric sequence=>string (arg)
  (:documentation "sequence=>string"))

;; sequenceクラス全般
(defmethod sequence=>string ((arg sequence))
  (format t "sequence -> ~A" arg)
  arg)

;; listに特化 ここでは単に次に特定できるものを呼んでるだけなのであまり意味なし
(defmethod sequence=>string ((arg list))
  (call-next-method))

;; theでstringかどうかをチェック
(defmethod sequence=>string :around ((arg sequence))
  (let ((res (call-next-method)))  ;theに直接全部詰め込むと、難しくて判定できないらしい。
    (the string res)))

;; 実験
(sequence=>string "foo")
>>> sequence -> foo
(sequence=>string '(foo bar baz))
error:>>> The value (FOO BAR BAZ) is not of type STRING.

2008-04-17

突発性CL勉強会

| 20:02 | 突発性CL勉強会 - わだばLisperになる を含むブックマーク はてなブックマーク - 突発性CL勉強会 - わだばLisperになる

「なんだか、最近CLが流行ってるらしいよ。色んなところで勉強会もあるみたいだし…」という風説を流布するため、突発的ですがCLの勉強会を呼び掛けてみることにしました!

人も集まらないだろうし、一回ごとに完結できるテーマの方が良いだろうということで、

  1. Edi Weitz氏のTHE POWER OF LISP MACROS
  2. Peter Norvig、Kent Pitman両氏のTutorial on Good Lisp Programming Style(日本語訳あり)
  3. The Common Lisp Cookbookの中のFundamentals of CLOS(日本語訳CLクックブックさん)
  4. ASDFの設定、使い方〜周辺ユーティリティついてとASDFインストール可能な簡単なプロジェクトの作成方法。
  5. FORMATか、LOOPマクロをマスターする
  6. SLIMEって便利だよ
  7. 数理システムさんの「Common Lisp における例外処理 〜Condition System の活用〜」

のどれか一つ位を教材にすることを考えていて、私を含めて定員は3名。参加条件としては、勉強会の内容をできるだけ詳細にブログに書くこと(読んだ人が勉強会に参加する必要がない位の詳細さがベスト)としたいと思います。

日時は、今週か来週の土曜日で、場所は、渋谷、新宿、世田谷区のどこか集まれそうな場所等を考えています。

参加しても良いよ!という方は、このエントリにコメント頂ければと思います。

誰も集まらない場合、1人3役でキャラ設定をしつつ私が喫茶店とかで勉強します!

GOOでL-99 (P12 ランレングス圧縮の伸長)

| 17:45 | GOOでL-99 (P12 ランレングス圧縮の伸長) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P12 ランレングス圧縮の伸長) - わだばLisperになる

何となくDylanの書法を真似て書いてみました。DGは、CLのdefgeneric、Dylanのdefine genericです。

generic系で、対応するクラスと返り値のクラスを明示→個別をmethod系で定義、というのは、CLOS系では割と定番なのかもしれません。

GOOでは、let(def)は、タプルを指定することによって分割代入的なことが可能なので使ってみました。

それと、letはCL/Schemeでいうlet*なので若干注意が必要かもしれません。パラレルにしたい場合は、前述のタプルを使った方法で書く必要があります。

opfは、Arcのzap、TAOの!!のようなもので自己代入の書法です。(set x (op + _ 1) ) => (opf x (+ _ 1) )と書けます。

(decode '((4 a) (1 b) (2 c) (2 a) (1 d) (4 e)))
;=> (a a a a b c c a a d e e e e)
(decode #((4 a) (1 b) (2 c) (2 a) (1 d) (4 e)))
;=> #(a a a a b c c a a d e e e e)
(decode #[(4 a) (1 b) (2 c) (2 a) (1 d) (4 e)])
;=> #[a a a a b c c a a d e e e e]
(decode "4;a,1;b,2;c,2;a,1;d,4;e")
;=> "aaaabccaadeeee"

(dg decode (u|<seq> => <seq>))

(dm decode (u|<seq> => <seq>)
  (def res () )
  (for ((item u))
    (def (tup n item) item)
    (opf res (cat _ (repeat `(,item) n))))
  (as (class-of u) res))

(dm decode (u|<str> => <str>)
  (let ((res "")
        (items (split u #\,)))
    (for ((x items))
      (def (tup n item) (split x #\;))
      (opf res (cat _ (repeat item (str-to-num n)))))
    res))

Getting Started in *LISP (5)

| 15:35 | Getting Started in *LISP (5) - わだばLisperになる を含むブックマーク はてなブックマーク - Getting Started in *LISP (5) - わだばLisperになる

実に2ヶ月も間が空いてしまいました…。

もう、自分自身すっかり忘れてしまっていますが、

Getting Started in StarLispという*LISPのチュートリアルの実習をしています。

とりあえず、毎週木曜日には、このシリーズでエントリを書くことにしました。

1.2.4 Mortal or Immortal

前回までは、ムーア型とノイマン型のオートマトンを作成して様子を観察していましたが、今回は、それぞれ絶滅するパターンはあるか?を探るようです。

どうやって集計するかですが、*LISPには、*SUMというpvarの数を集計してくれる関数があるので、それを使います。

(*sum (signum!! *automata-grid*))

SIMD版signumで、0、1、-1に割り振って集計というわけですね。-1はありえないので、個体数を勘定するのに使えます。

それで、それをユーティリティとして定義してみます。

(defun deadp ()
  (zerop (*sum (signum!! *automata-grid*))))

これで、絶滅したかを確認することができます。ここで、zeropと、zerop!!の使い分けについての説明があります。

今回の場合、*sumは、PVARを返すわけではなく、スカラー値を返すので、zeropを使うのが適切とのこと。

これらを使って絶滅するパターンを探ってみよう、という感じです。

次回は、1.2.5からです。

2008-04-16

返り値の型を明示してコンパイラの気を引く試み

| 15:55 | 返り値の型を明示してコンパイラの気を引く試み - わだばLisperになる を含むブックマーク はてなブックマーク - 返り値の型を明示してコンパイラの気を引く試み - わだばLisperになる

DylanでもGOOでも(もちろんGOOはDylanの影響大)、定義時に返り値の型をチェックするように書くことができる。

割と良い感じだと思うので、CLでやったらどんな感じになるのかなと思って作ってみた。

(defmacro defunt (name (&rest args) => type &body body)
  (unless (eq => '=>) (error "foo!"))
  (let ((/result (gensym)))
    `(eval-when (:compile-toplevel :load-toplevel :execute)
       (setf (symbol-function ',name)
             (lambda ,args
               ((lambda (,/result)
                  (the ,type ,/result))
                (block ,name
                  (locally
                      ,@body)))))
       ',name)))

あまり考えてない定義だけど、とりあえず

(defunt fib (n) => list
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))

;>>> Asserted type LIST conflicts with derived type (VALUES NUMBER &OPTIONAL).

のように書くと、コンパイルの時点で型が競合していることを教えてくれたりはする。

多値に対応するにはもう一捻り必要…。

DylanでL-99 (P02 最後2つの要素)

| 14:42 | DylanでL-99 (P02 最後2つの要素) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P02 最後2つの要素) - わだばLisperになる

マニュアルを読んでも、あまり書法が理解できないので、参考にできそうなコードを探してみたところ、DylaのCL風ライブラリというものを発見。

作者は、Scott MacKay氏ですが、元Symbolicsの人で、現在ITAに所属しているらしいというLISP街道まっしぐらな方のようです。

Dynamic Languages Wizards Series, Spring 2001にもDavid Moon氏等と一緒にパネリストとして出演してたのを見たことがあります。

とりあえず、真似するのが良いかなと思って、MacKay氏のスタイルを真似。

最初にgenericを作成して、後で特定化されたmethodを追加し、Dylanでは、返り値の型を明示することができるのですが、そこはきっちり書くというスタイルのようです。

let list = #(foo:, bar:, baz:);
format-out("%=\n", last2(list));
// => #(#"bar", #"baz")

let list = #();
format-out("%=\n", last2(list));
// => #()

// Code
module: l99-02

define generic last2
    (sequence :: <sequence>) 
 => (result :: <sequence>);

define method last2
    (sequence :: <list>)
 => (result :: <list>)
  if (2 >= size(sequence))
    sequence
  else
    last2(tail(sequence))
  end if
end method last2;

比較としてCLOSでも考えてみました。

Dylanと違ってdefgenericで返り値の型は指定できないようなので、documentationを付けてるだけです(笑)

返り値の型の指定はどうやったら良いかなと思いましたが、theを付ければ良いだけなんでしょうか。どういうのが定石なんでしょう…。

もしくは、aroundや、afterメソッドで返り値の型をチェックする、なども可能だったりするんでしょうか?

返り値の型を明示するというのは、CLのようにインクリメンタルにコンパイル可能で会話的に開発できる言語でも、割と御利益が多いんじゃないかと思うのですが、どうでしょう。

自分はコンパイラが教えてくれる情報が多くなるので、なんとなく好みです。

(defgeneric last2 (sequence)
  (:documentation "last2"))

(defmethod last2 ((sequence list))
  (if (>= 2 (length sequence))
      (the list sequence)
      (last2 (cdr sequence))))

DylanでL-99 (P01 最後のペアを返す)

| 01:27 | DylanでL-99 (P01 最後のペアを返す) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P01 最後のペアを返す) - わだばLisperになる

自分は何度かDylanに挑戦してはいるのですが、処理系のインストールの時点で挫折したり、動いてもコンパイルの仕方をすぐ忘れたり、S式でないので文法を憶えられなかったりして何度も挫折しています。

…ということで、L-99なのですが…。

Dylanには複数の処理系があります。Gwydion Dylanと、Open Dylanがあるのですが、今回はOpen Dylanにしておきます。

どうしてかというと、Open Dylanで最近SWANK(SLIMEのバックエンド)が動いてるらしく、SLIMEで開発できるっぽいので、Open Dylanの方が面白そうだということで…。

とりあえず、

http://www.opendylan.org/downloads/opendylan/1.0beta4/

にパッケージがあるので、ダウンロードしてインストールします。

これだけで動くようになりました。

それで、Dylanの開発環境なのですが、この辺も謎なところです。

昔のDylanのスクリーンショット等をみると非常にリッチな開発環境を持っていたようなのですが、フリーでもこういう環境はあるのでしょうか。

とりあえず、追々探って行くことにして、Emacsで書いてコンパイル、という方向で行きます。

Open Dylanの場合、make-dylan-appコマンドがインストールされます。

好きなディレクトリで、例えば、l99-01というプロジェクトの場合、

$ make-dylan-app l99-01

とすると、l99-01というディレクトリが作成され、その中に色々謎なものが生成されます。

それで、ソースファイルは、l99-01.dylanなのですが、これはHello, Woldのテンプレートになっています。

とりあえず、これを編集して、makeファイルも自動生成されるので、ソースを書いたらmakeする、ということになるみたいです。

Dylanでは、

シンボル→foo: もしくは、#"foo"

リスト→#(foo:, bar:, baz)

のようです。

formatのような、format-outがあるんですが、書法は、Cのprintf的です。

headは、carで、tailはcdr

empty?は、空かどうかを判定する総称関数。

コメントは、//で、C++っぽいです。

ちなみに、今回、一番驚いたのは、はてなのシンタックスハイライトにdylanがあったことです(笑)

let list = #(foo:, bar:, baz:);
format-out("%=\n", my-last(list));
// => #(#"baz")

// Code
module: l99-01

define function my-last(list)
  if (empty?(tail(list)))
    list;
  else
    my-last(tail(list));
  end if;
end function my-last;

2008-04-15


LISP1.5でL-99 (P03 K番目の要素)

| 19:00 | LISP1.5でL-99 (P03 K番目の要素) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P03 K番目の要素) - わだばLisperになる

最初は<や>はLISPには存在していなくて、greaterp、lesspでした。

また、1+、1-は、add1、sub1でした。MacLISPや、Lispマシン位までは両方使えましたが、Common Lispで廃止されたようです。

;  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; ELEMENT-AT
;
; ((A B C D E) 3)

; END OF EVALQUOTE, VALUE IS ..
; C

DEFINE ((
(ELEMENT-AT (LAMBDA (LST K) 
              (COND ((NULL LST) () )
                    ((GREATERP 2 K) (CAR LST))
                    (T (ELEMENT-AT (CDR LST) (SUB1 K))))))
    ))

ELEMENT-AT((A B C D E) 3)

CodeReposデビュー

| 18:36 | CodeReposデビュー - わだばLisperになる を含むブックマーク はてなブックマーク - CodeReposデビュー - わだばLisperになる

なんとなく面白そうだったのでCodeReposデビューしてみた。

特に作るものがなかったので、xyzzyにdestructuring-bindがなかったことを思い出してSBCLから移植してみることに。

SBCLからの移植だと、思ったより芋蔓式に色々なマクロが必要になってしまった。

他に良いのはないかと思ってCADRのコードを眺めてみたところ、比較的コンパクトに纏まっていたので、それを移植。

30年前のコードだけど…。

移植といっても、エラーの書式をちょっと合せて、古いLisp関数を補助関数として加えただけ。

xyzzyのパッケージの扱いにちょっとはまった。

そして、fixdapのxyzzyプロジェクトにも参加してみることにした。

2008-04-14

LISP1.5でL-99 (P02 最後2つの要素)

| 20:37 | LISP1.5でL-99 (P02 最後2つの要素) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P02 最後2つの要素) - わだばLisperになる

下のコードの例では、セミコロンをコメント記号として評価結果を書いていますが、LISP 1.5はコメント記号は持っていないため(バッチ処理だから?)、実際にはコメントになりません。

また、前回、LISP 1.5は大文字と小文字を区別しないと書きましたが、区別しないのではなくて、大文字しか存在していないので区別できない、というのが正確かもしれません。

ソースファイルをBCDに変換してIBM7094エミュレータに読み込ませているのですが、この変換時に全部大文字に直してくれているようなので、エミュレータへの入力は混合でOKです。

;  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; LAST2
;
; ((FOO BAR BAZ QUUX))

; END OF EVALQUOTE, VALUE IS ..
; (BAZ QUUX)

DEFINE ((
(LAST2 (LAMBDA (LST)
         (COND ((GREATERP 3 (LENGTH LST)) LST)
               (T (LAST2 (CDR LST))))))    ))

LAST2((FOO BAR BAZ QUUX))

2008-04-13

LISP1.5でL-99 (P01 最後のペアを返す)

| 20:37 | LISP1.5でL-99 (P01 最後のペアを返す) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P01 最後のペアを返す) - わだばLisperになる

今年は、LISP生誕50年であり、色々やるなら、やはりLISP1.5は外せないだろう…、ということで…。

全部大文字で書いてますが、LISP 1.5も大文字と小文字は区別せず、エミュレータに読み込ませるソースは小文字で書いても大丈夫なので、大文字にする必要はありません。

気分というか趣味の問題ですね…。

; FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
; LAST
;
; ((FOO BAR BAZ))
;
; END OF EVALQUOTE, VALUE IS ..
; (BAZ)

DEFINE((
(LAST (LAMBDA (LST) 
        (COND ((NULL (CDR LST)) LST)
              (T (LAST (CDR LST))))))
))

LAST((FOO BAR BAZ))

APL由来の関数

| 20:17 | APL由来の関数 - わだばLisperになる を含むブックマーク はてなブックマーク - APL由来の関数 - わだばLisperになる

dropで思い出しけれどLISP畑にはAPL由来の関数名は結構あるようで、自分が知ってる範囲では、iota、take、drop、reduceがある。他にもあったりするのだろうか。

でも、あまり良いネーミングとは思えない(笑)

pfcでL-99 (P02 最後2つの要素)

| 19:43 | pfcでL-99 (P02 最後2つの要素) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P02 最後2つの要素) - わだばLisperになる

自分は毎回混乱しているのですが、2番目の問題は、最後2つの要素を取り出す問題です。

標準でdropがあるので、それを使ってみました。

dropはSRFI-1由来だと思いますが、CLだと、LASTがdropの代わりに使えます。

(last '(1 2 3 4) 2) => (3 4)

(last2 [1 2 3 4])
;=> [3 4]

(def (last2 lst)
  (drop (- (length lst) 2) lst))

(def (last2 lst)
  (if (>= 2 (length lst))
      lst
      (last2 (cdr lst))))

CLで学ぶ「プログラミングGauche」 (6)

| 18:13 | CLで学ぶ「プログラミングGauche」 (6) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (6) - わだばLisperになる

今回は6章「リスト」です。

6.1 リストの二つの顔

ここはCL/Scheme共通だと思います。

6.2 リストの基本操作

()のcar、cdr適用は動作が違っているのは、CL/Schemeでの違いでも割と有名な事項だと思います。

CLでは、伝統に則って空リストにcarとcdrを適用すると、()が返ります。

色々深遠な理由があるとも言われているようですが、LISP 1.5では、このような動作ではなく、PDP-1 LISPからかと思っていましたが、(cdr () )は、()のプロパティが返るのでnilが返って来ている訳ではありませんでした。そうなると、PDP-6 LISP以降だと思うのですが、いまいちはっきりしません。まあ、誰も拘ってないと思いますが(笑)

()にcarや、cdrを適用してもエラーにならないというのは、便利なところもありますが、

(defun foo (lst)
  (when (atom (car lst))
    (car lst)))

のように書くと、

(foo ())
;=> nil
(foo '(()))
;=> nil

では、区別がつかないし、エラーにもならないしで気付かないバグを入れていることが自分は多いです。ということで

(defun *car (lst)
  (when (null lst)
    (warn "()にcarを適用しました。"))
  (car lst))

のようなものを作って使ってみたりもしますが、いまいちです。

ちなみに、TAOでは、デフォルトの挙動を大域変数で変更できてエラーにすることも、nilを返すこともできたようです。

  • null?、pair?

CLでは、null、pair?はそれぞれnullと、conspになります。

最後に、-pが付くか、-?が付くかはSchemeと伝統的LISPの習慣の違いかと思いますが、atomと、nullには、-pが付かないので一貫性がなかったりします。これは、LISP 1.5からそうなので、50年来の伝統というほかないんでしょう。処理系によっては、atompや、nullpもあったりするようです。

空リストかを判定する関数としては、nullの他に、endpがあります。endpは、リスト以外を与えるとエラーになります。

6.3 リストの操作

  • fold => reduce

ここでは、foldがでてきます。CLには、foldという名前の関数はありませんが、同じような機能としてreduceがあります。

;; fold
(fold <手続き> <初期値> <リスト> ... <リストN>)

;; reduce / CL
(reduce <手続き> <リスト> :initial-value <初期値>)

reduceでは、初期値をキーワードで与えることと、複数のリストを与えることができないというところが違っています。

また、このセクションででてくる+inf.0、-inf.0のようなものはCLにはありません。

most-negative-〜定数があるので、それが近いといえば近いのかもしれませんが、どうなのでしょう。

  • 内部define => flet、labels

恐らく、Schemeでは、名前空間が一つで変数/関数の名前の衝突を避ける必要があるのでローカル関数は多用される傾向があるんじゃないかと思います。

CLでは空間が別々で変数と関数の名前は衝突せず、割と大らかに大域で定義してしまうことが多いように思います。

個人的には、関数内関数は個別にデバッグするのが面倒に感じるので、大域で定義することが多いです。まあ、デバッグが済んでから合体すれば良いのですが…。

また、CLでは、さらにパッケージを分割することもできるので、名前空間の汚染に関してはそれほど気にする必要はないのかもしれません。

「いちいちfuncallを付けなくてはいけない面倒臭ささ」と「名前の衝突回避のノウハウを頭の片隅に常駐させて置く負担」はトレードオフな気もします。

そして、CLでは、defunの中でdefunを使うような書き方は普通されず、

(defun max-number (lst)
  (defun pick-greater (a b)
    (if (> a b) a b))
  (reduce #'pick-greater lst
          :initial-value most-negative-long-float))

(defun max-number (lst)
  (flet ((pick-greater (a b)
           (if (> a b) a b)))
    (reduce #'pick-greater lst
            :initial-value most-negative-long-float)))

下の例のように、fletや、再帰する場合、labelsを使ってローカル関数を定義します。

内部defunは以前のSBCLでは警告が出たと思ったのですが、今回改めて試してみると警告はでなくなっているようです。

他の処理系でも警告は出ないのですが、これは書法としてOKなのでしょうか…。

6.4 foldの定義

リストの操作を学習する上で、foldの定義をしてみるというのは教材として良いアイディアではないかと思いました。

ここで#?=を使用したデバッグプリントがでてきますが、CLにはないので、適当に以前作ったものを、また載せてみます。

(defmacro debug-print (obj &optional name (output t))
  `(let ((name (if ,name ,name 0)))
     (format ,output "~&#?=[~A]:~A~%#-~4T~A~%" name ',obj ,obj)
     ,obj))

(defun gauche-debug-print (stream char arg)
  (declare (ignore char))
  (if (char= #\= (peek-char t stream))
      (read-char stream))
  `(debug-print ,(read stream t nil t) ,arg t))

(set-dispatch-macro-character #\# #\? #'Gauche-debug-print)

;; #?=を仕込む
(defun fold (proc init lst)
  (if (null lst)
      init
      (fold proc
            #111?=(funcall proc (car lst) init)
            #555?=(cdr lst))))

;; 使ってみる
(fold #'+ 0 '(1 2 3 4 5))
;>>>
;#?=[111]:(FUNCALL PROC (CAR LST) INIT)
;#-  1
;#?=[555]:(CDR LST)
;#-  (2 3 4 5)
;#?=[111]:(FUNCALL PROC (CAR LST) INIT)
;#-  3
;#?=[555]:(CDR LST)
;#-  (3 4 5)
;#?=[111]:(FUNCALL PROC (CAR LST) INIT)
;#-  6
;#?=[555]:(CDR LST)
;#-  (4 5)
;#?=[111]:(FUNCALL PROC (CAR LST) INIT)
;#-  10
;#?=[555]:(CDR LST)
;#-  (5)
;#?=[111]:(FUNCALL PROC (CAR LST) INIT)
;#-  15
;#?=[555]:(CDR LST)
;#-  NIL

Gaucheのようにソースファイルの行を表示することは、難しかったのですが、ディスパッチマクロ文字は、10進数の引数が取れるので番号付けで代用してみています。

しかし、CLには、普通にtraceがあるので、

(trace fold)

で、

  0: (FOLD #<FUNCTION (SB-C::&OPTIONAL-DISPATCH +) {10007DEFC9}> 0 (1 2 3 4 5))
    1: (FOLD #<FUNCTION (SB-C::&OPTIONAL-DISPATCH +) {10007DEFC9}> 1 (2 3 4 5))
      2: (FOLD #<FUNCTION (SB-C::&OPTIONAL-DISPATCH +) {10007DEFC9}> 3 (3 4 5))
        3: (FOLD #<FUNCTION (SB-C::&OPTIONAL-DISPATCH +) {10007DEFC9}> 6 (4 5))
          4: (FOLD #<FUNCTION (SB-C::&OPTIONAL-DISPATCH +) {10007DEFC9}> 10 (5))
            5: (FOLD #<FUNCTION (SB-C::&OPTIONAL-DISPATCH +) {10007DEFC9}> 15
                     NIL)
            5: FOLD returned 15
          4: FOLD returned 15
        3: FOLD returned 15
      2: FOLD returned 15
    1: FOLD returned 15
  0: FOLD returned 15

のように表示することもできるので、普通はこっちを使えば良いかなと思います。

6.5 簡単なリスト処理

ここでは、CLの関数で言えば、last、copy-list、copy-tree/練習問題、append、reverse、find-ifを自作することによってリスト処理を学びます。

deep-copy-listは、CLの標準関数でいうと、copy-treeになるかと思います。

一応、課題を書いてみました。

(defun deep-copy-list (lst)
  (if (consp lst)
      (cons (deep-copy-list (car lst))
            (deep-copy-list (cdr lst)))
      lst))
  • char-alphabetic? => alpha-char-p

char-alphabetic?は、CL標準では、alpha-char-pとして存在しています。

  • find => find-if

ここでのfindは、CLでは、find-ifです。

また、condの説明がでてきますが、Schemeのようにelseがキーワードになってはいません。

慣例的にtを書きますが、CLでは、nil以外は全部真なので、'Tでも'elseでも:elseでもOKです。

また、RSR6風の括弧の使い方ですが、CLにはこのようなものはないので欲しい場合は、自作することになると思います。

(defun open-bracket-macro-char (stream macro-char)
  (declare (ignore macro-char))
  (read-delimited-list #\] stream t))
(set-macro-character #\[ #'open-bracket-macro-char)
(set-macro-character #\] (get-macro-character #\)))

;; 使用例
(defun my-find-if (pred lst)
  (cond [(null lst) nil]
        [(funcall pred (car lst)) (car lst)]
        [:else (my-find-if pred (cdr lst))]))

6.6 2種類の再帰

末尾再帰とそれ以外の再帰についてのセクションです。

Schemeでは末尾再帰が最適化されるというところは、Schemeのプログラミング書法にはかなり大きく影響していると思います。

CLでは、最適化することを義務付けてはいないので、処理系によってするものもあればしないものもあるといった感じです。

ということで、末尾再帰が必ず最適化されることを期待して書くということは推奨されていないようで普通にループで書くことが多いようです。

イレギュラーなところでは、

(defun len (lst)
  (prog ((lst lst) (n 0))
    =>  (return (if (null lst)
                    n
                    (progn (setq lst (cdr lst) n (1+ n))
                           (go =>))))

のように書けば、関数呼び出し気分なgotoを書けるので、もの好きな方にはお勧めしたいです(笑)

このブログでも、末尾再帰の最適化機構付きのtail-recursive-defunというマクロを古いMacLISPのコードからみつけて動作を考えてみたことがありましたが、マクロのレベルで構文を解析して、末尾再帰的記述をループに書き換えるというのはどの程度有効なのでしょう。

どうしても、変数の代入と束縛というところが違ってきてしまう気はしますが…。

2008-04-12

GOOでL-99 (P11 要素をランレングス圧縮する その2)

| 09:13 | GOOでL-99 (P11 要素をランレングス圧縮する その2) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P11 要素をランレングス圧縮する その2) - わだばLisperになる

前回(P10)の内容をちょっと変更して終了

(encode-modified '(a a a a b c c a a d e e e e))
;=> ((4 a) b (2 c) (2 a) d (4 e))
(encode-modified "aaaabccaadeeee")
;=> "4;a,b,2;c,2;a,d,4;e"

(dm encode-modified (u|<col> => <col>)
  (as (class-of u)
      (map (fun (x) (let ((xlen (len x)))
                      (if (= 1 xlen)
                          (head x)
                          `(,xlen ,(head x)))))
           (my-pack1 u))))

(dm encode-modified (u|<str> => <str>)
  (join (map (fun (x) (let ((xlen (len x)))
                        (cat (if (= 1 xlen) "" (cat (to-str xlen) ";"))
                             (to-str (head x)))))
             (my-pack1 u))
        ","))

pfcでL-99 (P01 最後のペアを返す)

| 07:38 | pfcでL-99 (P01 最後のペアを返す) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P01 最後のペアを返す) - わだばLisperになる

なんとなく面白そうだったので、pfcでも、L-99に挑戦してみることにしました。

GaucheのWilikiのページで、letzf等の内包表記が使える処理系ということで知ったのですが、処理系探してもずっと見付けられないでいました。

処理系の名前がどうやら、pfcらしいということが分かって、pfcで検索していたらGaucheのLingr部屋のログに配布先があったので、ソースを入手して試してみました。

ソースは、Darcsで入手できます。

darcs get http://www.sampou.org/repos/pfc

srcディレクトリの中で、makeすれば、pfcの実行ファイルが生成されますので、それを実行します。

ドキュメントはないようですが、電通大の岩崎教授が中心となって、Haskell/Schemeで有名な山下氏、伊東氏が開発に参加している処理系のようです。

まだ良く分かっていないのですが、標準で遅延評価やambがあったりするようです。

pfcでの関数定義は、Schemeと同じようなのですが、defineの代わりにdefも使えるようで、MIT記法もあります。

リスト、タプル/ベクタ

リストはCL/Schemeと違ってGOOのように真性リストしかないようです。

ドット対リストに相当するものは、(pair 1 1) =>{1 1}のように作る様子。要素にはfstとsndでアクセスでQiのタプルと同じ感じ。

また、(list 1 2 3)の代わりに、[1 2 3]と書くところもQiに似ています。

細々したこと

null ≡ null?、cdr ≡ tl(tailから?)、car ≡ hd(headから?)、cons? ≡ consp等、MacLISP系の関数名とScheme畑や関数言語畑の名前が好みに応じて使えるようです。

(my-last '(a b c d))
;=> (d)

(def (my-last lst)
  (if (null (tl lst))
      lst
      (my-last (tl lst))))

(def (my-last lst)
  (drop (1- (length lst)) lst))

(def (my-last lst)
  [((! lst) (1- (length lst)))])

;; ~~~
(index '(0 1 2 3) 0) ; or (! '(0 1 2 3) 0)
;=> 0

(let ((!lst (! '(0 1 2 3))))
  (!lst 0))
;=> 0

2008-04-11


QiでL-99 (P26 指定した個数を抜き出す組み合わせ)

| 08:14 | QiでL-99 (P26 指定した個数を抜き出す組み合わせ) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P26 指定した個数を抜き出す組み合わせ) - わだばLisperになる

Qiでは今迄あまり無名関数を使ってませんでしたが、(lambda (x) x)は、Qiでは、(/. X X)と書きます。/.をλと見立てているようなんですが…。

(combination 3 [a b c d e f])
\=> [[a b c] [a b d] [a b e] ... ]
\

(length (combination 3 (range 1 12)))
\=> 220
\

(define combination 
  0 Lst -> [ ] 
  1 Lst -> (map (/. X [X]) Lst)
  N Lst -> [ ] where (> N (length Lst))
  N [H | T] -> (append (map (/. X [H | X]) (combination (- N 1) T))
                       (combination N T)))

GOOでL-99 (P10 ランレングス圧縮)

| 07:32 | GOOでL-99 (P10 ランレングス圧縮) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P10 ランレングス圧縮) - わだばLisperになる

P09の定義を利用してランレングス圧縮を作成します。

今回も、コレクションクラスで作成。

前回作成した補助関数my-pack1を利用しました。

GOOの総称関数のディスパッチはCLOSのようにより特定なクラスが優先されます。

下記の例でも二つのメソッドのうちストリングクラスの定義の方が上位クラスのコレクションクラスより特定的なのでそちらが優先されています。

funは、CL/Schemeのlambdaで、Arcのfnのようなネーミングです。

Arcの[foo _]に対応する形式としては、(op foo _)というものがあります。

(encode '(a a a a b c c a a d e e e e))
;=> ((4 a) (1 b) (2 c) (2 a) (1 d) (4 e))
(encode #(a a a a b c c a a d e e e e))
;=> #((4 a) (1 b) (2 c) (2 a) (1 d) (4 e))
(encode #[a a a a b c c a a d e e e e])
;=> #[(4 a) (1 b) (2 c) (2 a) (1 d) (4 e)]
(encode "aaaabccaadeeee")
;=> "4;a,1;b,2;c,2;a,1;d,4;e"

(dm encode (u|<col> => <col>)
  (as (class-of u)
      (map (fun (x) `(,(len x) ,(head x)))
           (my-pack1 u))))

(dm encode (u|<str> => <str>)
  (join (map (fun (x) (cat (to-str (len x)) ";" (to-str (head x))))
             (my-pack1 u))
        ","))

2008-04-10

CLで学ぶ「プログラミングGauche」 (5.5)

| 09:03 | CLで学ぶ「プログラミングGauche」 (5.5) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (5.5) - わだばLisperになる

今回は5.5章から再開です。

一連のエントリを読み返してみると、あたかもCLについて詳しく知っているように書いてしまっている気がしますが、実際のところ全然詳しくないので、間違いがあったら是非ツッコんで頂けると嬉しいです。

5.5 名前と予約語

Schemeには予約語がないそうですが、CLはどうなんでしょう。調べても良く分かりませんでした。

特に予約語という記述もないので、似たような状況だとは思うのですが…。

システムの根幹に関わるものの名前を再定義することは可能だったりはしますが、警告やエラーになったりもします。

また、メジャーな処理系では拡張でパッケージをロックする仕組みもあり、根幹のCOMMON-LISPパッケージ等の内容を上書きで再定義しようとすると、パッケージロックのエラーになります。

5.6 モジュールを使う

GaucheではSchemeの拡張としてモジュールの仕組みがありますが、CLでは標準のパッケージの仕組みが相当するかと思います。

ただ、CLのパッケージは、名前空間を分けるということが主で、モジュールのロードの仕組み等まで規定するものではありません。

Emacsのように、PROVIDEや、REQUIREというものもありますが、Emacsのようにロードする仕組みがきっちりと決まっている訳ではなくて具体的にファイルをロードする仕組みは処理系依存になります。中途半端に悩ましいことも多い気もしますが、最近では、ASDFというモジュールをロードする仕組みがメジャーになりつつあるようで処理系によっては、標準でREQUIREがASDFを呼び出すようになっているものもあります(SBCL等)。

PROVIDEは*modules*にパッケージ名を追加登録するだけのものです。割と、provideでなく(pushnew :foo *modules*)などと書かれていることも多く、また、ASDFでロードされたパッケージも*modules*に追加する人もいれば、いない人もいるという感じでなんだか適当な現状です。

また、CLは#+、#-等で処理系の違いを吸収する記述が可能で、依存個所を分けて記述し、一つのソースを色々な処理系で読み込ませることが普通に行われています。

これは、MacLISPから続く伝統であり、CL、MacLISP、Zetalisp、Franz Lisp等、MacLISP系のLISPであれば、共通のソースファイルを使用することも可能です。(それなりに面倒なこともありますが…。)

5.7〜5.8 SRFI

SRFIはSchemeの共通のライブラリとして非常に有効に機能していますが、CLの人々にはそのような共通のライブラリを持つという考えは薄いのか、割とばらばらな様子です。元から抱えてるものが多いというのもあるでしょう。CLRFIというSRFIを手本にした動きもありますが、活溌ではないようです。

とはいえ、最近では、ASDFが共通の基盤になりつつあるので、定番なものは、ASDFで簡単に導入できるようになっていることが殆どです。

また、ネットワークインストール等に対応したASDF-INSTALLもあり、Debianのapt-getのように、(asdf-install:install :foo)でインストール可能で非常に便利ではあります。

CLのANSI標準が決まってから月日も流れ、今日必要とされている機能が規定されていないことなどが色々と議論されることも多くなってきているようです。

現状CLに足りていないものについては、CL界の大御所であるDaniel Weireb氏が纏めている下記のエントリの後半が非常に参考になります。

ということで、次回は、6章から再開したいと思います。

GOOでL-99 (P09 連続して現われる要素を纏める)

| 04:23 | GOOでL-99 (P09 連続して現われる要素を纏める) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P09 連続して現われる要素を纏める) - わだばLisperになる

今回も無駄にリストだけでなくコレクションクラスに対応してみました。

また、GOOでは、繰り返しの為の値の蓄積にpackerというものが使えるようなので使ってみました。

packer-fabでpakerのインスタンスを作成し、pack-inで蓄積、packedで蓄積結果を任意のクラスで返します。割とややこしい使い勝手。


(my-pack '(a a a a b c c a a d e e e e))
;=> ((a a a a) (b) (c c) (a a) (d) (e e e e))
(my-pack #(a a a a b c c a a d e e e e))
;=> #((a a a a) (b) (c c) (a a) (d) (e e e e))
(my-pack #[a a a a b c c a a d e e e e])
;=> #[(a a a a) (b) (c c) (a a) (d) (e e e e)]
(my-pack "aaaabccaadeeee")
;=> "aaaa,b,cc,aa,d,eeee"

(dm my-pack (u|<col> => <col>)
  (if (empty? u)
      u
      (as (class-of u) (my-pack1 u))))

(dm my-pack (u|<str> => <str>)
  (if (empty? u)
      u
      (join (map (op as <str> _) 
                 (my-pack1 u))
            ",")))

(df my-pack1 (u|<col> => <lst>)
  (let ((prev (1st u))
        (res (packer-fab <lst>))
        (tem (packer-fab <lst>)))
    (for ((x u))
      (unless (= x prev)
        (pack-in res (packed tem))
        (set tem (packer-fab <lst>)))
      (pack-in tem x)
      (set prev x))
    (pack-in res (packed tem))
    (packed res)))

;))))))))))

QiでL-99 (P25 ランダムに並び換え)

| 02:36 | QiでL-99 (P25 ランダムに並び換え) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P25 ランダムに並び換え) - わだばLisperになる

(rnd-permu [a b c d e f])
\=> [e a d f c b]
\

(define rnd-permu
  U -> (rnd-select U (length U)))

2008-04-09

CLで学ぶ「プログラミングGauche」 (5)

| 04:33 | CLで学ぶ「プログラミングGauche」 (5) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (5) - わだばLisperになる

今回は5章「プログラムの書式」です。

5.1 スクリプト

Gaucheは主にスクリプト言語として開発されていますが、CLの場合、イメージベースなのでスクリプトとして呼ばれるというところは得意としていないと思われます。

全く不可能ではないですが、そこに重きを置いていないようなので手薄になっていて、そういう用途で使う場合には特有の問題に遭遇することが多いです。

この辺の解説は、LispUser.netさんの記事が非常にためになります。

個人的には、UNIXソフトウェアツールの一員としてCLを使うのは、色々面倒臭いので逆にSLIMEからOSを操作しています(LISP側からホストを呼ぶ)。

とはいえ、そういう需要もあるので処理系によっては便利な拡張があります。

CLISPでは、

#!/usr/bin/env clisp

(defun main ()
  (format t "さようなら、世界!!~%")
  (print *args*))

(main)

のようなスクリプトを書いて、実行属性を付けてあげれば、

./hello.lisp "hello" "hello"
さようなら、世界!!

("hello" "hello")

という風に簡単に実行できます。

SBCLにはこの仕組みはありませんが、割と簡単に近いものは作れる気がしたので、作ってみました。

自分は、この方法について良く分かっていないので、もっと良い方法があると思います。

下記の方法は、CL-USERパッケージにファイルをロードして終了するmainという名前の関数を定義し、ssbclというスクリプトからmain関数を呼び出しているだけのものです。

内容はマニュアルのSBCLをシェルスクリプトとして実行するところから拝借しました。

1. 毎回設定をロードさせるのも面倒なので、専用のイメージを作成
;; このファイルをsbcl --load shebang.lispのようにして実行すると、
;; /tmp/shebang.coreが生成される
(in-package :cl-user)

;; #!を読み飛ばす
(set-dispatch-macro-character #\# #\!
			      (lambda (stream char arg)
				(declare (ignore char arg))
				(read-line stream)))

;; デバッガを抑制する
(setf *invoke-debugger-hook* 
      (lambda (condition hook)
	(declare (ignore hook))
	;; Uncomment to get backtraces on errors
	;; (sb-debug:backtrace 20)
	(format *error-output* "Error: ~A~%" condition)
	(quit)))

;; スクリプトが存在するか確認する
(defun probe-script ()
  (and (second *posix-argv*)
       (probe-file (second *posix-argv*))))

;; main / スクリプトを実行して終了する関数
(defun main ()
  (let ((script (probe-script)))
    (when script
      (load script)
      (quit))))

;; イメージを"/tmp/shebang.core"に生成する
;; sbcl --core shebang.coreで起動
(sb-ext:save-lisp-and-die "/tmp/shebang.core" :purify t)
2. 起動用のスクリプトを作成
#!/bin/sh

sbcl --noinform --core shebang.core --eval '(main)' $*
3. 実行したいスクリプト例
#!/home/dir/bin/ssbcl

(defun foo ()
  (format t "さようなら、世界!!~%~A" *package*)
  (print *posix-argv*))

(foo)
4. 試してみる
% ./hello.lisp
>>> さようなら、世界!!
>>> #<PACKAGE "COMMON-LISP-USER">
>>> ("/usr/local/sbcl/1.0.15/bin/sbcl" "./hello.lisp")

5.2 マルチバイト文字の利用

前回のエントリとダブってしまうのですが、CLでも日本語を取り扱うことは可能です。処理系依存だったりして色々面倒なところもあるのですが、SBCL、CLISP、Clozure CL、Allegro CL、LispWorks等主要なところは各種文字コードに対応しているので大抵は大丈夫だと思います。

これまたLispUserさんの記事が非常に参考になります。

5.3 コメント

行コメントも、ブロックコメントもCLと共通です。S式コメントはないので、必要な場合は、リードマクロで作ってみるのも面白いかもしれません。

あまり深く考えて作ってないですが、

(defun S-expression-comments (stream char arg)
  (declare (ignore char arg))
  (read stream t nil t)
  (values))

(set-dispatch-macro-character #\# #\; #'s-expression-comments)

こんな感じで良いのでしょうか。ちなみに、最後に(values)を実行して姿を消すというのは反則かなあとずっと思っていたのですが、CLtL2の22章にも例として載っているのをみつけたので割と定番なのかもしれません。

S式コメントに近いものとしては、LISP古来からの方法だと、

(print "Hello")
=>
'(print "Hello")

という風にクオートしてしまうというのが近いといえば近いかもしれません。

;; 行コメント
(print "Hello")				;行コメント

#| ブロックコメント
  
  #|ネスト可能 |#

|#

;; 自作S式コメント
#;(progn (princ "さようなら、")
       (princ "世界!!")
       (terpri))

(list 1 #;2 3)
=> (1 3)

;; クオートでも似たことは可能、しかし当然ながら返り値は発生する。
'(progn (princ "さようなら、")
       (princ "世界!!")
       (terpri))

5.4 空白

ここも概ね一緒です。

という感じで、長くなったので5.5からは次回にします。

GOOでL-99 (P08 連続して現われる要素を圧縮)

| 01:24 | GOOでL-99 (P08 連続して現われる要素を圧縮) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P08 連続して現われる要素を圧縮) - わだばLisperになる

コレクションクラスに対応してみました。

コレクションクラスがどういう位置付けなのかいまいち分かっていませんが、リストやベクタのスーパークラスのようです。

1stはCLのfirstで、2nd、3rdまで標準で存在しています。

empty?は、コレクションクラスのオブジェクトが空であるかを判定するものです。

(compress "") ;=> ""
(compress '(a a a a b c c a a d e e e e)) ;=> (a b c a d e) リスト
(compress #(a a a a b c c a a d e e e e)) ;=> #(a b c a d e) タプル
(compress #[a a a a b c c a a d e e e e]) ;=> #[a b c a d e] ベクタ
(compress "aaaabccaadeeee") ;=> "abcade" 文字列

(dm compress (u|<col> => <col>)
  (if (empty? u)
      u
      (let ((prev (1st u))
            (but1st (sub u 1 (len u)))
            (res (lst prev)))
        (for ((x but1st))
            (unless (= x prev)
              (pushf res x)
              (set prev x)))
        (as (class-of u) (rev res)))))

QiでL-99 (P24 ロトくじ)

| 00:35 | QiでL-99 (P24 ロトくじ) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P24 ロトくじ) - わだばLisperになる

Qiでも、もう少しでリスト篇が終わり!

(lotto-select 6 49)
\=> [29 5 24 18 23 2]
\

(define lotto-select
  N M -> (rnd-select (range 1 M) N))

2008-04-08

GOOでL-99 (P07 リストの平坦化)

| 06:38 | GOOでL-99 (P07 リストの平坦化) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P07 リストの平坦化) - わだばLisperになる

flattenは、恐らくリストにしか使わないので、総称関数ではなく普通の関数にしてみました。

リストかどうかを判定する関数が見付からなかったのでlst?を作成。

また、GOOでは、引数の指定で戻り値の型をDylanのような記法で明記することができるので折角なので使ってみました。

pairはCL/Schemeでいうところのconsなのですが、GOOは慣例的な名前を悉く否定しているとしか思えません(笑)

(flatten '(1 () 2 3 4 5 6 ((7((((())))))) (8)))
;=> (1 2 3 4 5 6 7 8)

(df flatten (u|<lst> => <lst>)
  (cond ((nul? u) () )
        ((lst? (head u))
         (cat (flatten (head u))
              (flatten (tail u))))
        (#t (pair (head u)
                  (flatten (tail u))))))

(df lst? (u|<any> => <log>)
  (subtype? (class-of u) <lst>))

QiでL-99 (P23 ランダムに指定した個数の要素を選択)

| 05:07 | QiでL-99 (P23 ランダムに指定した個数の要素を選択) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P23 ランダムに指定した個数の要素を選択) - わだばLisperになる

今迄なんとなくQiっぽくなくなるかなと思ってあまりifを使ってきませんでしたが、Qiにもifはあります。

今回は、whereだけでは、lengthを2回呼ぶことになるのでifを使ってみました。

(rnd-select [a b c d e f g h] 3)  
\=> [g c e]
\

(define rnd-select
  [ ] _ -> [ ]
  Lst N -> (let Len (length Lst)
             (if (> N Len)
                 [ ]
                 (rnd-select* Lst N [ ] Len))))

(define rnd-select* 
  [ ] _ Acc _ -> Acc
  Lst N Acc Len -> Acc where (>= 0 N)
  Lst N Acc Len -> (let Pos (+ 1 (random Len))
                     (rnd-select* (remove-at Lst Pos)
                                  (- N 1)
                                  [(nth Pos Lst) | Acc]
                                  (- Len 1))))

CLで学ぶ「プログラミングGauche」 (4)

| 03:25 | CLで学ぶ「プログラミングGauche」 (4) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (4) - わだばLisperになる

すっかり間があいてしまいました。

今回は4章「Gaucheの開発スタイル」です。

4.1 インタラクティブな実行とREPL

4.1.1 リテラル

このセクションに関しては大体CLも同じですが、複素数の表記方法等ちょっとした違いもあります。

それと、文字のエンコーディングについては実装依存になっているので、処理系ごとに確認する必要がありますが、大体は、external-formatで指定することになるようです。

に詳しく纏められています。

;; 浮動小数点数
3.14

;; 有理数
2/3

;; 複素数
#C(3 4)

;; 真(nil以外のすべて)
t

;; 偽
nil

;; 文字
#\a

;; 日本語 (実装依存)
#\あ
#\HIRAGANA_LETTER_A

;; SBCLでの例
sb-impl::*default-external-format*
=> :utf-8

(code-char 12354)
=> #\HIRAGANA_LETTER_A
(princ (code-char 12354))
>>> あ

;; CLISP、SBCL、Clozure CLでは#\u3042とった表記が可能
(princ #\u3042)
>>> あ

;; 制御文字等
#\Space

#\Newline

;; 文字列
"abc"
4.1.2 手続き呼び出し

このセクションもSchemeのdefineとCLのdefunは意味が違っていること以外は大体共通です。

CLのdefunは、名前に関数の実体を束縛する以外にも色々なことを行っていて、この辺は、LISP-1と、LISP-2とで違いがあります。

;; Scheme: (define sqrt2 (sqrt 2))

(setq sqrt2 (sqrt 2))
;; もしくは
(defparameter sqrt2 (sqrt 2))
(defvar sqrt2 (sqrt 2))
;; 等色々状況によって使い分けが必要

;; 手続きの定義
(defun pythagoras (x y)
  (sqrt (+ (* x x) (* y y))))

(pythagoras 3 4)
=> 5

;; (exit)
;; 大体cl-userで定義されていることが多い。
;; quitだったり、exitだったり処理系によって違う。

4.2 Emacs

ここでLispマシンの話がでてきますが、EmacsのS式操作/開発環境はLispマシンから引き継いだ操作というよりは、最初の1970年代後半のITS EMACSで完成していて、ZMACSは、もちろんEMACSの影響を受けているので、ITS EMACS→ZMACSではなかったかなと思います。ただ同時期なのではっきりしたことは言えません。…非常にどうでも良いことで誰も興味ないと思いますが、敢えて喰いついてみました(笑)

実際のところ、表4-2の快適な移動コマンドの例とC-M-\は30年以上前のオリジナルEMACSに既に全部あるので、もしかしたら、EMACSより前のエディタマクロ集の時点で既に定番だったのかもしれません。また、GNU Emacsのinferior lispモードくらいの開発環境は、PDP-10上でEMACSとMacLISPが連携するLEDITで既に実現していました。

EMACS上のCLの開発環境といえば、最近では、SLIMEが有名ですが、この章で解説されていることは殆どカバーしています。

SLIMEが便利なところとして思い付くところでは、

  • キー操作一つで式ごとコンパイルできる
  • M-.で定義したソースファイルに飛ぶことができる。これは処理系がサポートしていれば、なのですが、Allegro Cl、SBCL、Clozure CLはサポートしています。詳しくは、404 Not Foundに纏められています。ちなみに、この機能は、30年前のMIT CADR Lispマシンに既に存在します。
  • コンパイルしてエラーになったところを色付き下線で表示してくれて、M-p等で該当個所に飛べる(警告とエラーで色が別)
  • シンボル/関数を補完してくれる(補完方式には、色々あってd-bi→destructuring-bindというものや、dbind→destructuring-bind等あり)
  • 関数等のドキュメント文字列を呼び出して綺麗に表示
  • 調べたいシンボルにカーソルを合せてキー操作一つでHyperSpecが引ける
  • 自動で編集しているファイルのパッケージを認識してくれ、パッケージを移動してくれる。
  • カーソル位置のマクロの展開を行なってくれる(マクロ展開表示の上でさらにc-c c-m可能)(再帰的に最終形態まで一度に展開することも可能)
  • 調べたい関数のシンボルにカーソルを合せてキー操作一つでdisassembleの結果を表示してくれる。
  • Emacsや、Xyzzyの*scratch*バッファのように、式を評価すると結果が挿入されるようなモードがある
  • サーバ/クライアント方式なので、同一イメージに複数人が接続して開発することが可能。

等々。

このうちで、自分が気に入っているのは、マクロの展開表示です。

文章で書くと便利そうでもないかもしれませんが、

(do ((i 0 (1+ i)))
    ((= 10 i) 'end)
  (print i))

というマクロがあったとすると、カーソルを式の先頭にもっていってキーを操作すれば、

(BLOCK NIL
  (LET ((I 0))
    (TAGBODY (GO #:G1)
     #:G0    (TAGBODY (PRINT I))
             (PSETQ I (1+ I))
     #:G1    (UNLESS (= 10 I) (GO #:G0)))
    'END))

と別窓で表示してくれるという機能です。さらに、別窓の式も芋蔓式にマクロ展開できます。

この機能は非常に便利だと思うのですが、LISP使いの方と話しても、そんなのREPLで

(macroexpand-1 
 '(do ((i 0 (1+ i)))
     ((= 10 i) 'end)
   (print i)))

ってすれば良いんだよ、とか言われてしまい、便利だと思われないことが多いです。

なんでなんだー!?。まあ、自分の説明が悪いのかもしれません。

という感じで、5章に続きます。

2008-04-07


MacLISPでは表示と入力の基数にローマ数字が設定できた

| 19:49 | MacLISPでは表示と入力の基数にローマ数字が設定できた - わだばLisperになる を含むブックマーク はてなブックマーク - MacLISPでは表示と入力の基数にローマ数字が設定できた - わだばLisperになる

前回のPAIP勉強会の後のちょっとした飲み会でも話のネタにしたのですが、MacLISPは、システムの基数にローマ数字を指定できました。

非常に馬鹿っぽくて大好きなので、これをネタに、またpartty.orgでキャプチャーしてみました。

どういうことかというと、MacLISPでは、表示用の基数をbase、入力用の基数をibaseで指定可能で標準は、8(8進数)です。

ここに、romanという指定をすると、iや、mcm等が、1や、1900として扱われることになります。

ちなみに、入力と表示は分かれているので、入力と表示で一致しないこともあります。

この機能は単にハックバリュー(つまり、ただ作ってみたかったから作っただけ)の為に追加されたとのことで、ジャーゴンファイルのhack valueの項目にも説明があります。

基数にローマ数字を設定した場合、iや、xが数字になるので、変数の扱いには注意が必要です。また、MacLISPのマニュアルによると、コンパイルの時の挙動も予測できないものになりがちなので基数にローマ数字を設定するな、というような注意書きがあったりします。

ちなみに、formatのローマ数字のサポート機能も、この基数をローマ数字にするのもGuy Steel氏の仕業だったと記憶しているのですが、改めて資料を探しても見付かりませんでした…。夏休みに実装した、ってのをどっかで読んだ筈なんですが、詳細を知ってる方がいたら教えて下さい…。

なんにしろ、注意書きを書くことはあっても、こんな機能を削除しないというMacLISPは素晴しいですね。

2008-04-06


QiでL-99 (P22 指定した範囲の数列のリスト)

| 18:18 | QiでL-99 (P22 指定した範囲の数列のリスト) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P22 指定した範囲の数列のリスト) - わだばLisperになる

(range 0.5 10)
\=> [0.5 1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5]
\

(define range 
  Start End -> [ ] where (> Start End)
  Start End -> [Start | (range (+ 1 Start) End)])

GOOでL-99 (P06 回文的かを調べる)

| 18:11 | GOOでL-99 (P06 回文的かを調べる) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P06 回文的かを調べる) - わだばLisperになる

意味なく、回文数にも対応してみました。

(palindrome '(x a m a x)) ;=> #t
(palindrome #(x a m a x)) ;=> #t
(palindrome "xamax")      ;=> #t
(palindrome 12321)        ;=> #t

(dm palindrome (u|<col>)
  (= u (rev u)))

(dm palindrome (u|<num>)
  (palindrome (num-to-str u)))

Arcでletrec、内部define

| 03:32 | Arcでletrec、内部define - わだばLisperになる を含むブックマーク はてなブックマーク - Arcでletrec、内部define - わだばLisperになる

ArcにはSchemeのletrecや、CLのlabelsに相当する構文がないのだけれど、

(def fact (n)
  (let f1 ()
    (= f1
       (fn (c acc)
         (if (is 0 c)
             acc
             (f1 (- c 1) (* c acc)))))
    (f1 n 1)))

のように書くことになるのだろうか。

同様に内部defineは、

(def fact (n)
  (let f1 ()
    (def f1 (c acc)
      (if (is 0 c)
          acc
          (f1 (- c 1) (* c acc))))
    (f1 n 1)))

のように書くことになるのだろうか。

どっちにしろ、letでローカル束縛を作れば、大域定義になるのを防げる。

2008-04-05

QiでL-99 (P21 指定した位置に要素を挿入する)

| 21:57 | QiでL-99 (P21 指定した位置に要素を挿入する) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P21 指定した位置に要素を挿入する) - わだばLisperになる

Qiにはバックトラックの構文もあって非決定性プログラミングの解説もチュートリアルにあります。

しかし説明が難しくて全然分からない(笑)

(insert-at alfa [a b c d] 2)
\=> [a alfa b c d]
\

(define insert-at
  Item [ ] _ -> [Item]
  Item Lst Pos -> [Item | Lst] where (>= 1 Pos)
  Item [H | T] Pos -> [H | (insert-at Item T (- Pos 1))])

ArcでL-99 (P55 左右のバランスがとれた二分木)

| 00:54 | ArcでL-99 (P55 左右のバランスがとれた二分木) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P55 左右のバランスがとれた二分木) - わだばLisperになる

今回は、左右のバランスがとれた二分木を生成するのがお題ですが、元がPrologの問題ということもあってバックトラックを使用して解くように、ということになっています。

バランスが取れていることの定義ですが、各々の部分木ごとにノード数が同じか、1つ違うだけ、とのこと。

ここは、Scheme風のバックトラックを使うかどうか迷いましたが、分からなくなったので前にCLで作ったものを移植しました(;´Д`)…。

これは、バックトラックではなくて、力技で全部の組み合わせのリストを生成します。

しかし、バックトラックを使って解けるようにならないと、この先かなり苦戦すると思うので、ここはちょっと保留して、Scheme風のバックトラックでどう書けるのか、考えてみた方が良いのかもしれない…。

細々

  1. 0と、0.0が同じものであると判定する方法が分からなかったので、(iso 0 0.0) -> nil、==というものを作って比較しています。
  2. Arcのreduceは初期値を設定できないので、redという初期値を設定できるreduceをでっちあげました。

(each p (cbal-tree 6)
  (prn p))
;=>
;(x (x (x nil nil) (x nil nil)) (x nil (x nil nil)))
;(x (x (x nil nil) (x nil nil)) (x (x nil nil) nil))
;(x (x nil (x nil nil)) (x (x nil nil) (x nil nil)))
;(x (x (x nil nil) nil) (x (x nil nil) (x nil nil)))
;nil

(def cbal-tree (n)
  (if (is 0 n) '(())
      (>= 1 n) '((x () () ))
      'else
      (red (fn (res x)
             (let tree `(x ,@x)
               (if cbal-tree-p.tree
                   `(,tree ,@res)
                   res)))
           () ;init
           (let half (/ (- n 1) 2)
             (if nofraction.half
                 ;; balance
                 (comb2 cbal-tree.half
                        cbal-tree.half)
                 ;; unbalance
                 (with (g (+ 1 trunc.half) ;greater 
                        l trunc.half)      ;less
                   `(,@(comb2 cbal-tree.l
                              cbal-tree.g)
                     ,@(comb2 cbal-tree.g
                              cbal-tree.l))))))))

(def nofraction (num)
  (== 0 (- num (trunc num))))

(def cbal-tree-p (tree)
  (let (ro l r) tree
    (>= 1 (abs (- count-leaf.l
                  count-leaf.r)))))

(def count-leaf (tree)
  (iflet (ro l r) tree
         (+ 1 count-leaf.l count-leaf.r))
         0)

(def comb2 (xs ys)
  (mappend (fn (y) (map (fn (x) `(,x ,y)) xs))
           ys))

(def red (f init lst)
  (reduce f (cons init lst)))

(def == (x y)
  (and (>= x y) (<= x y)))

2008-04-04


GOOでL-99 (P05 リストを逆転させる)

| 01:23 | GOOでL-99 (P05 リストを逆転させる) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P05 リストを逆転させる) - わだばLisperになる

今回もlstクラスとflatクラスに対応させてみました。

matchはパターンマッチで変数を束縛するものです。しかし、なんだかあまり使い勝手は良くないような…。

catは、concatenateの略で、append的なところはcatを使うようです。

asは型変換で、CLだと、coerceですが、恐らくDylanの命名法からとってきたんだと思います。

pushfはCLでいうpushですが、引数の順番が逆です。

微妙に色々違っていて非常に憶えづらい(;´Д`)…。

また、GOOには標準でrevがあります。

(my-rev '(1 2 3 4)) ;=> (4 3 2 1)
(my-rev "foo") ;=> "oof"
(my-rev #(a b c)) ;=> #(c b a)
(my-rev #[a b c]) ;=> #[c b a]

(dm my-rev (u|<lst>)
  (if (nul? u)
      ()
      (match u 
        ((,H ,@T) (cat (my-rev T) (lst H))))))

(dm my-rev (u|<flat>)
  (def res () )
  (for ((x u))
      (pushf res x))
  (as (class-of u) res))

QiでL-99 (P20 指定した要素を削除)

| 00:42 | QiでL-99 (P20 指定した要素を削除) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99 (P20 指定した要素を削除) - わだばLisperになる

若干ダレて来たような(;´Д`)…。

(remove-at [a b c d] 2)
\=> [a c d]
\

(define remove-at 
  [ ] _ -> [ ]
  X K -> X where (>= 0 K)
  [H | T] 1 -> T
  [H | T] K -> [H | (remove-at T (- K 1))])

2008-04-03

またふらふらとCADRでなにかする

| 19:08 | またふらふらとCADRでなにかする - わだばLisperになる を含むブックマーク はてなブックマーク - またふらふらとCADRでなにかする - わだばLisperになる

メインの環境を64bit Linuxにしてから、CADRエミュレータが上手く動かなくなっていたので放置していたのですが、Ubuntuの場合ia32-libsをインストールすれば、面倒もなく32bit版で動くことが分かったので、またいじり始めてみることにしました。

以前のエントリでは、ダウンロードから導入、ちょっとした開発環境の説明を書きました(CADRカテゴリーに纏めてあります)が、

今年は、もう一歩踏み込んでみたいところ。

目標としては、Flavorsなどを使ってプログラミングするところ位まで探究できたら良いなと思っています。

配布されているMIT CADRエミュレータの場合、初期のマニュアルに説明のある機能が無かったりしますので、結構初期のLisp Machine Lispのようです。

とりあえず今日は久し振りに使ってみるということで、前々から試してみたかった、ZMACSへのコマンドの追加を試してみたいと思います。

ZMACSは、Lispマシンで動くEMACSで、オリジナルのEMACSとほとんど同時期の1976年に開発がスタートしたようです。

最初は、EINE(Eine Is Not EMACS)という名前で、その後、ZWEI(Zwei Was Eine Initially)という名前になり、Zmacsへ、という流れになります。

Lispマシンは全面的にLispで記述されているので、当然その上で動くZMACSもLispで記述されています。LISPで記述されているということは、最初からLISPで拡張可能なわけで、恐らくLISPで拡張可能なエディタとしては、ZMACSが一番最初のものなのではないでしょうか。Multics EMACSは、1978年で、Multics EMACSの作者もZMACSの方が先に存在してたと言ってましたし…。

それはさておきZMACSの拡張コマンドですが、DEFCOMという対話操作拡張用のマクロが用意されているのでそれを使ってみます。

作るコマンドですが、自分は、普段ダブルクオートをM-"のキーバインドで、対で入力しています。

EMACSでは、幾つかこの様な入力方法を実現する方法があるのですが、ZWEIにもCOM-MAKE-()という、括弧を対で入力する関数があるので、これをちょっといじって作ってみました。

この関数は数引数を受けとることにより、任意の式をまたいで囲むことができます。

(pkg-bind "zwei"
  (DEFCOM COM-MAKE-/"/" "Insert matching delimiters, putting point between them.
With an argument, puts that many s-exprs within the new /"/"." ()
    (LET ((OPEN #/") (CLOSE #/")
          (MOVER 'FORWARD-SEXP) (POINT (POINT)))
      (DO ((CH (LDB %%CH-CHAR *LAST-COMMAND-CHAR*))
           (L *MATCHING-DELIMITER-LIST* (CDR L)))
          ((NULL L))
        (COND ((OR (= CH (CAAR L)) (= CH (CADAR L)))
               (SETQ OPEN (CAAR L) CLOSE (CADAR L) MOVER (CADDAR L))
               (RETURN T))))
      (LET ((BP (IF *NUMERIC-ARG-P*
                    (OR (IF (EQ MOVER 'FORWARD-SEXP)
                            (FORWARD-SEXP POINT *NUMERIC-ARG* NIL 0 NIL T T)    ;No UP
                          (FUNCALL MOVER POINT *NUMERIC-ARG*))
                        (BARF))
                  POINT)))
        (AND (MINUSP *NUMERIC-ARG*) (PSETQ BP POINT POINT BP))
        (INSERT BP (IN-CURRENT-FONT CLOSE))
        (INSERT-MOVING POINT (IN-CURRENT-FONT OPEN))
        DIS-TEXT)))
  )

ちょっとした説明

  • EMACSとZMACSの違い

上記のコードを眺めるとGNU Emacsとは結構違っていることが分かると思います。Hemlockや、LispWorksのエディタ、Climacs等これに近いのですが、恐らく、ZMACSから影響を受けていて、ZMACS系とも呼べると思います。

  • pkg-bind
    • ボディ部は指定したパッケージ内で評価されます。上記の場合、"ZWEI"パッケージ内で評価されています。Lispマシンの場合、ファイルの一行目にパッケージを記述することで、指定することができるのですが、この方法を発見したので試しに使ってみています。

それで、定義した関数をどうっやってキーバインドに割り付けるかですが、

割り付けには、SET-COMTABやSET-COMTAB-RETURN-UNDOを使用します。

自分は、初期化ファイルで設定するので、ログアウトしたらアンドゥされるように、SET-COMTAB-RETURN-UNDOを使ってみています。

(login-eval
 zwei:(set-comtab-return-undo *standard-comtab* '(#^C/" com-make-/"/"))) ;^Cはコントロール文字

という風にホームディレクトリのlispm.initに記述すれば、M-"でcom-make-""が呼び出されるようにログイン時に初期化されます。

ちなみに、パッケージの指定方法が不思議な感じになっていますが、こういう風にも書けたみたいです。

zwei:(set-comtab-return-undo *standard-comtab* '(#^C/" com-make-/"/"))
=>
(zwei:set-comtab-return-undo zwei:*standard-comtab* '(#^C/" zwei:com-make-/"/"))

ということのようなのですが詳細はまだ、良く分かっていません。pkg-bindの略記方法なのかもしれませんが、便利といえば便利なような。

という感じで、ふらふらとしばらくMIT CADRを使ってみようと思います。

2008-04-02


ArcでL-99 (P54a 二分木かどうかを判定)

| 21:21 | ArcでL-99 (P54a 二分木かどうかを判定) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P54a 二分木かどうかを判定) - わだばLisperになる

今回から二分木篇に突入です。番号はどういう訳かいきなり54a。

ここでの二分木とは、(x nil nil)という風に定義し、(根 葉 葉)というリストで表現されるとのことです。

木は根と葉から成り、根はアトム、葉は木から成ります。

それで今回のお題は、二分木になっているかを判定する関数を書けとのこと。

(atree '(1 2 3)) ;=> nil
(atree '(x nil nil)) ;=> t
(atree '(x (x nil nil) (x nil (x nil nil)))) ;=> t
(atree '(x (x nil nil) (x nil (x nil nil x)))) ;=> nil


(def atree (tree)
  (if atom.tree no.tree
      'else
      (and (is 3 len.tree) 
           (let (root left right) tree
             (and atom.root root
                  atree.left
                  atree.right)))))

2008-04-01


ArcのzapとTAOの!!

| 19:07 | ArcのzapとTAOの!! - わだばLisperになる を含むブックマーク はてなブックマーク - ArcのzapとTAOの!! - わだばLisperになる

Arcのzapは、

(set x (+ x 1))

のような代入を

(zap + x 1)

のように書けるようにする。確かに代入においてこのパターンは多いかもしれない。

C等での、+=等に相当する様子。

この構文で思い出すのが、TAOの!!で、TAOでは、上記を

(!!+ !x 1)

と書ける。

!xというのがミソでどの変数に代入するかを!を付けることで指定できるので、

(let ((x nil)
      (y 42))
  (!!cons y !x))
y ;=> 42
x ;=> '(42)

ということもできる。

改めてTAOの自己代入式を検索してみたら、以前リードマクロとしてCLで作った自分のエントリが出てきてしまった。

自分の関心はなんか同じところをぐるぐる回っているような…。

QiでL-99 (P19 指定した位置でローテーションさせる)

| 18:06 | QiでL-99  (P19 指定した位置でローテーションさせる) - わだばLisperになる を含むブックマーク はてなブックマーク - QiでL-99  (P19 指定した位置でローテーションさせる) - わだばLisperになる

今迄、Qiに1+とか、1-が備え付けで存在すると思っていましたが、なんとQiでは関数名を全部大文字で書くと、下層のCLの関数が直接呼べるんだそうで、1+はCLから呼んでいたのでした。また、DEFUNや、DEFMACROで下層のCLの関数も定義可能とのこと。

(my-rotate [a b c d e f g h] 3)
\=>  [d e f g h a b c]
\

(my-rotate [a b c d e f g h] -2)
\=> [g h a b c d e f]
\

(define my-rotate
  [ ] _ -> [ ]
  Lst N -> (xappend (split Lst (+ N (length Lst)))) where (> 0 N)
  Lst N -> (xappend (split Lst N)))

(define xappend
  [H T] -> (append T  H))

GOOのdef

| 00:09 | GOOのdef - わだばLisperになる を含むブックマーク はてなブックマーク - GOOのdef - わだばLisperになる

GOOではdefを使って、

(df foo (n)
  (def x (* n 2))
  (lst x))

のように、囲いのないletのような感じで使うことができるのが面白い、と思っていたが、良く考えると、schemeでも、

(define (foo n)
  (define x (* n 2))
  (list x))

と書けば良いだけだった。

Arcでは、当初"="で

(def foo (n)
  (= x (* n 2))
  (list x))

同じことができるようにするつもりだったと7年前の試案では書いてあるが、現状のArcでは、ローカルで宣言された変数以外に代入した場合、トップレベルの値を書き換えることもある。

;; scheme
(define (foo n)
  (let ()
    (define x (* n 2)))
  (list x))

(foo 8) ;=> error

;; GOO
(df foo (n)
  (let ()
    (def x (* n 2)))
  (list x))

(foo 8) ;=> error

;; Arc
(def foo (n)
  ((fn () 
    (= x (* n 2))))
  list.x)
(foo 8) ;=> (16)

x ;=> 16