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-01-31

お題(127): 指定コマンドを別プロセスで起動、Arcで参加etc

| 20:09 | お題(127): 指定コマンドを別プロセスで起動、Arcで参加etc - わだばLisperになる を含むブックマーク はてなブックマーク - お題(127): 指定コマンドを別プロセスで起動、Arcで参加etc - わだばLisperになる

Common Lisp部門

今回のお題は、指定コマンドを別プロセスで起動とのこと。

とりあえず、KMRCLのcommand-outputを紹介して終わりました。

Arc部門

もっと「俺も俺も」とドッカンドッカン投稿されるのかと思ってたんですが、今日現在盛り上がってるのは、アジア圏内参加者では私一人だけのようです。

関数名等がCommon Lisp系のものが殆どなので、Common Lispに馴染みがあるとなんとなく気軽かなという気はしています。

そんな感じなので、Schemeの人とかは、Schemeの標準関数とかSRFIを片っ端から作ってしまうという学習方法もアリな気もします(笑)

決められた枠が非常にゆるい気がするので、やりたい放題な印象も受けますし…。

あと、システムのソースのarc.arcを眺めるとやりたいことが、存外さっと見付かって良い感じです。

それはさておき、そういった感じで、簡単なのをつらつらと投稿しております。

  • Hello, world!

とりあえず、昨日のうちに記念で投稿しました。

  • 与えられた数字のケタ数

文字列にして勘定の方向で

  • コラッツ・角谷の問題

前にCLで作ったのを翻訳してみたんですが、元が遅いので動作が遅いままです。

afnってのがあるんですが、結構便利な気がします。

ネーミングはitの代りにselfで参照できるアナフォリック無名関数ってことなんでしょうか。

((afn (x)
   (if (< x 2)
       x
       (+ (self (- n 1))
	  (self (- n 2)))))
 2)

とか。

  • 重複無し乱数

Common Lispのrotatefを使った感じで作ってみました。

  • 重複する要素を取り除く

CL版をそのまま書き直し

  • アレイのuniq

元からあるので、それを紹介

  • n人中m人が当選するくじ

「重複する要素を取り除く」の解答を流用してやっつけ気味

Arcを使ってみてのちょっとしたこと
  1. 全体的に短く書けるだけに、無闇に短くしたくなる。
  2. 関数名が大体3文字に略された単語なので、自分が付ける名前もそんな感じになって行く…。
  3. cdr:memとか割と良い感じに思える。多分馴れると常套句が沢山できる気がする。とりあえず、引数を1つしか取らないのを見付けると、すぐ合体してみたくなる。
  4. (lst 3)とかも案外良い感じ、要するに、lst[3]ということなのだけれど…。
  5. (push (pop (nchcdr lst (rand (len lst)))) res)とかしたかったけど、現状では駄目らしい。

末尾再帰的DEFUN (2)

| 04:53 | 末尾再帰的DEFUN (2) - わだばLisperになる を含むブックマーク はてなブックマーク - 末尾再帰的DEFUN (2) - わだばLisperになる

何となく釈然としないまま、一旦放置した末尾再帰的DEFUNですが、何となく眺めていると末尾再帰をgo-toに変換するんじゃないのかなあ、という気がしてきました。

つまり明示的に末尾再帰で書かれたものを、完全なループに変換するという目的のものだったのではないかと思えてきました。

そう考えると、関数呼び出しの個所をgo-toに変換すれば良いのですが、

(defun fib (n &optional (a1 1) (a2 0))
  (if (< n 2)
      a1
      (fib (1- n) (+ a1 a2) a1))))

のような末尾再帰の定義は

(defun fib (n &optional (a1 1) (a2 0))
  (prog ()
    L   (if (< n 2)
	    (return a1)
	    ((lambda (t1 t2 t3) (setq n t1 a1 t2 a2 t3) (go L)) (1- n) (+ a1 a2) a1)))))

のようにすれば、マクロで置き換えるのも、そんなに大変でもないかなと。

本当は、

(defun fib (n &optional (a1 1) (a2 0))
  (prog ()
    L   (if (< n 2)
	    (return a1)
	    (progn (setq n (1- n) a1 (+ a1 a2) a2 a1) (go L))))))

という風にするべきな気もします。スタックの使われ方とか、その辺に違いがありそうですが、disassemしても良く分からなかったので、とりあえず、lambdaの方で行くことにしました。

それで、この場合、PROGの中に展開されるので、最終的に値を返すところには、returnを付けないといけない訳なのですが、それがどこなのか判別するのは至難の技なので、逆にRETURNの中に展開してしまうことにしました。オリジナルもこういう感じなのですが、こういうことなのかも知れません。

(defun fib (n &optional (a1 1) (a2 0))
  (prog ()
    L   (return (if (< n 2)
		    a1
		    ((lambda (t1 t2 t3) (setq n t1 a1 t2 a2 t3) (go L)) (1- n) (+ a1 a2) a1))))))

そんなこんなでいつものごとくガチャガチャと自分なりに作ってみました。

RETURN式の中から外にgotoとかして良いのかしら、とか思ったりしますが、これって手法としてはありななんですかねえ。

lispList

末尾再帰的DEFUN

| 02:12 | 末尾再帰的DEFUN - わだばLisperになる を含むブックマーク はてなブックマーク - 末尾再帰的DEFUN - わだばLisperになる

今日は、Arcもいじっていたのですが、なんとなくSAILのMACLISPのコードも漁っていました。

SAILのものは非常に野心的というか、変態的というか、妙なコードが多いのですが、ふと以前から気になっていたTAIL-RECURSIVE-DEFUNのコードを追っ掛けてみることにしました。

実際のコードはこちらです。

SAILの変態っぷりは、恐らくRichard P.Gabriel氏によるところが非常に大きいと思うのですが、何となくこのTAIL-RECURSIVE-DEFUNもそんな香りがします。

とりあえず、探ってみたいのは、このコードです。

(DEFUN (TAIL-RECURSIVE-DEFUN MACRO)(X)
  ((LAMBDA(?F-NAME *TYPE)
    ((LAMBDA(*ARGS *DEFINITION)
      ((LAMBDA(?GO-LABEL)
	(α-GRAB-TAILS *ARGS *DEFINITION ?GO-LABEL)
	(CCODE (DEFUN ?F-NAME *TYPE (*ARGS) (PROG NIL
						  ?GO-LABEL
						  (RETURN (PROGN *DEFINITION))))))
       (GENSYM)))
     (COND (*TYPE (CADDDR X))(T (CADDR X)))
     (COND (*TYPE (CDDDDR X))(T (CDDDR X)))))
   (CADR X)
   (COND ((MEMQ (CADDR X) '(EXPR FEXPR))
	  (LIST (CADDR X)))
	 (T NIL))))
 ;

これは一体何をするものなのか。自動で末尾再帰に変換してくれるのか。それとも他に末尾再帰的な何かの特長があるのか、謎です…。

とりあえず、もの凄くLAMBDAがネストしているのですが、これはLETの役割です。それで、DEFUNになっているのですが、MACLISPでは、DEFUNでマクロも定義でき、この場合、マクロを定義しています。

最終的には、(defun foo (n) ...body)のように展開されたものができるんじゃないかと思います。

それでこのTAIL-RECURSIVE-DEFUNが依存している関数で独自に定義されたものを追っ掛けてみます。

とりあえず、MACLISPからCLへ移植してみました。CLにないMACLISP標準は自作しています。

(DEFUN ANY-MEMQ(X Y)
  (COND ((NULL Y)NIL)
	((ATOM Y)(EQ X Y))
	(T(OR (ANY-MEMQ X (CAR Y))
	      (ANY-MEMQ X (CDR Y))))))

;(any-memq 'x '(y (((((x(((())))))))) z))
;=> t

(defmacro ccode (X) `(DO-CODE ,x))

(DEFUN DO-CODE(X)
  (COND ((NULL X)NIL)
	((ATOM X)
	 ((LAMBDA(CHAR1)
	   (COND ((MEMQ CHAR1 '(? *))X)
		 (T (LIST 'QUOTE X))))
	  (GETCHAR X 1)))
	((AND (ATOM (CAR X))(EQ '* (GETCHAR (CAR X) 1)))
	 (LIST 'APPEND (DO-CODE (CAR X)) (DO-CODE (CDR X))))
	(T(LIST 'CONS (DO-CODE (CAR X)) (DO-CODE (CDR X))))))

(DEFUN α-GRAB-TAILS (ARGS DEF ?GO-LABEL)
 (COND ((ATOM DEF)NIL)
       ((AND (ATOM(CAR DEF)) (EQ 'TAIL-RECUR (CAR DEF)))
	(COND ((EQUAL ARGS (CDR DEF))		;calling with same args!
	       (RPLACA DEF 'GO)
	       (RPLACD DEF (LIST ?GO-LABEL)))
	      (T(DO ((ARGS ARGS (CDR ARGS))
		     (NEWARGS (CDR DEF) (CDR NEWARGS))
		     (SETS NIL (NCONC SETS
				      (COND ((EQ (CAR ARGS) (CAR NEWARGS))
					     NIL)
					    (T (NCONS
						((LAMBDA(SYM)
						  (CONS (CONS (CAR ARGS)SYM)
							(LIST 'SETQ
							      (CAR ARGS)
							      (SUBLIS (MAPCAR 'CAR
									      SETS)
								      (CAR NEWARGS)))))
						 (GENSYM))))))))
		    ((NULL ARGS)
		     ((LAMBDA(L-EXP)
		       (RPLACA DEF (CAR L-EXP))
		       (RPLACD DEF (CDR L-EXP)))
		      (α-OPTIMIZE-λ (MAPCAR 'CDAR SETS)
				    (NCONC (MAPCAR 'CDR SETS)
					   (NCONS(LIST 'GO ?GO-LABEL)))
				    (MAPCAR 'CAAR SETS))))))))
       (T(MAPC (FUNCTION(LAMBDA(DEF)
			 (α-GRAB-TAILS ARGS DEF ?GO-LABEL)))
	       DEF))))

(DEFUN α-OPTIMIZE-λ (VARS BODY BINDINGS)
  (DO ((VARS VARS (CDR VARS))
       (BINDINGS BINDINGS (CDR BINDINGS))
       (NVARS NIL (NCONC NVARS
			 (COND ((ANY-MEMQ (CAR VARS) BODY)(NCONS (CAR VARS)))
			       (T NIL))))
       (NBINS NIL (NCONC NBINS
			 (COND ((ANY-MEMQ (CAR VARS) BODY)(NCONS (CAR BINDINGS)))
			       (T NIL)))))
      ((NULL VARS)(CONS (CONS 'LAMBDA (CONS NVARS BODY))
			NBINS))))

;; オリジナルに割と忠実版
(defmacro TAIL-RECURSIVE-DEFUN (&whole X &body body)
  (declare (ignore body))
  ((LAMBDA(?F-NAME *TYPE)
     ((LAMBDA(*ARGS *DEFINITION)
	((LAMBDA(?GO-LABEL)
	   `(progn
	      ,@(α-GRAB-TAILS *ARGS *DEFINITION ?GO-LABEL)
	      (DEFUN ,?F-NAME ,*TYPE (,@*ARGS) (PROG NIL
						  ,?GO-LABEL
						  (RETURN (PROGN ,@*DEFINITION))))))
	 (GENSYM)))
      (COND (*TYPE (CADDDR X))(T (CADDR X)))
      (COND (*TYPE (CDDDDR X))(T (CDDDR X)))))
   (CADR X)
   (COND ((MEMQ (CADDR X) '(EXPR FEXPR))
	  (LIST (CADDR X)))
	 (T NIL))))

;; バッサリとMACLISP特有の部分を切り捨てた版
(defmacro TAIL-RECURSIVE-DEFUN (?f-name *args &body *definition)
  (let ((?GO-LABEL (gensym)))
    `(progn
       ,(α-GRAB-TAILS *ARGS *DEFINITION ?GO-LABEL)
       (DEFUN ,?F-NAME (,@*ARGS) 
	 (PROG NIL
	    ,?GO-LABEL
	       (RETURN (PROGN ,@*DEFINITION)))))))


;; ML標準の関数達
(defun ncons (n) (list n))

(defun memq (x y)
  (member x y :test #'eq))

(defun getchar (x index)
  (values (intern (string (char (string x) (1- index))))))
 ; 

中身の動作なのですが、とりあえず、

(α-GRAB-TAILS '(x y z) '(tail-recur) 'go)
;-> ((LAMBDA () (SETQ X NIL) (SETQ Y NIL) (SETQ Z NIL) (GO GO))) 

(α-GRAB-TAILS '(x y z) '(tail-recur x) 'go)
;-> ((LAMBDA () (SETQ Y NIL) (SETQ Z NIL) (GO GO))) 

(α-GRAB-TAILS '(x y z) '(tail-recur y z x) 'go)
;-> ((LAMBDA (#:G2999) (SETQ X Y) (SETQ Y Z) (SETQ Z #:G2999) (GO GO)) X) 

(α-GRAB-TAILS '(x y z) '( y z x) 'go)
;-> (Y Z X) 

(do-code '(?foobarbaz hello one))
;(CONS ?FOOBARBAZ (CONS 'HELLO (CONS 'ONE NIL))) 

(do-code '(*foobarbaz hello one))
;(APPEND *FOOBARBAZ (CONS 'HELLO (CONS 'ONE NIL))) 

(tail-recursive-defun xyz (x y z)
  tail-recur
  (list x y z))
; マクロ展開結果
;->
(PROGN
 ((LAMBDA () (SETQ X (LIST X Y Z)) (SETQ Y NIL) (SETQ Z NIL) (GO #:G2995)))
 (DEFUN XYZ (X Y Z)
   (PROG ()
    #:G2995
     (RETURN
      (PROGN
       ((LAMBDA ()
          (SETQ X (LIST X Y Z))
          (SETQ Y NIL)
          (SETQ Z NIL)
          (GO #:G2995))))))))

という感じです。

α-OPTIMIZE-λは、どうやら不要な変数束縛を取り除いて簡略化するもののようで、これは理解できました。

これを呼び出しているα-GRAB-TAILSが良く分からないのですが、名前からすると、末尾部分を抽出するもののようなのですが、動きが良く分からない…。

何がどう末尾再帰なのか…、纏められないままエントリを終わります(笑)

2008-01-30

Arc公開!

| 19:22 | Arc公開! - わだばLisperになる を含むブックマーク はてなブックマーク - Arc公開! - わだばLisperになる

Arcが、本当にこの冬に公開されました!

結構話題にもなってるようですね。

マイコミにも取り上げれるとは思いませんでした。(いつもマニアックな記事を書いてる方ではあるんですが)

しかし、画面写真は、なんでわざわざArcからSchemeに抜けてHello, World!を実行しているんだろう(笑)

とりあえず、早速インストールしてみます!

インストール

からソースをダウンロード

とりあえず、MzSchemeをUbuntuでパッケージからインストールしたところバージョンは、360でした。

推奨は、バージョン352とのことなのですが、面倒なのでとりあえず360で起動。

ざっとチュートリアルを眺めてみましたが、7年前の発表から比べると、なんとなく、とんがっていたところが丸くなってCommon Lisp風味が増した気がします。

On Lispでお馴染のものが沢山定義されているので、Common LispとOn Lispに馴染んだ人は親近感を覚えるんじゃないかと思います。

条件式がデフォルトでアナフォリックでなくなったり、若干寂しいですが、マクロの中で使うことなどを考えると厄介な気もするのでこれで良いのでしょう。

とはいえ、どの辺までが、コアの部分で、どこからがライブラリなのか良く分からないので、上記の評価も間違っているような(笑)

(aif "World!"
     (prn (+ "Hello, " it))
     (prn it))
;=> Hello, World!

とはいえ、aif等は予め用意されています。

  • 色々試してみる
('(foo bar baz) 0)
;=> foo

("012345" 4)
;=> #\4

(let h (fn (n) (repeat n (prn "Hello")))
  (h 5))
;=>
;Hello
;Hello
;Hello
;Hello
;Hello
;nil

(with (inc (annotate 'mac (fn (n) `(= ,n (+ ,n 1))))
       num 5)
  (inc num))
;=> error
;うーん、駄目。

(do (= foo (annotate 'mac (fn (n) `(= ,n (+ ,n 1)))))
    (let n 3
      (foo n)
      (foo n)))
;=> 5
;これは動いたけど、ローカルのマクロじゃないなあ。

うーん、自分の一番の関心はファースト・クラスのマクロ採用案ってのはどうなったのかということなのですが、変数にマクロを格納するのってどうやるんですかね。

それはさておき、とりあえず、記念にどう書くorgにHello,World!を投稿!

((pr "Hello, World!\n")で投稿しましたが、改行付きは、prnで書けた模様…。)

2008-01-28

LOOPマクロと内包表記とINTERLISP

| 20:05 | LOOPマクロと内包表記とINTERLISP - わだばLisperになる を含むブックマーク はてなブックマーク - LOOPマクロと内包表記とINTERLISP - わだばLisperになる

言語内に組み込まれたミニ言語の代表格として語られることが多い、LOOPマクロ

Common Lispの特長を良く表わしているとも言われますが、このLOOPマクロが初めて導入されたのは、Common Lispより以前のMACLISP系の処理系で、Lisp Machine Lisp、MACLISP、NIL等の共通の繰り返し機構として使われていたようです。*1

そんなLOOPマクロですが、CLtLでは、最初にloopという無限ループの枠組だけ提供して、後にフルセットで提供されることになったようです。

CLより前に後にフルセットのLOOPマクロはあったので、最初からフルセットで導入すれば良いようなものですが、導入された後も好き嫌いで色々言われる位なので、当時も色々あったのでしょう。

それで、そのオリジナルのLOOPマクロですが、MACLISP起源ではなく、INTERLISPの繰り返し機構の"FOR"とのこと。*2

構文は大体、Common LispのLOOPと同じ感覚で、

INTERLISP-10  31-Dec-84 ...
Hi.
3_(for i from 0 to 100 collect i)

(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100)
4_

のように機能します。

LOOPというキーワードが無いだけで割とそのまんまです。

そんなINTERLISPのFORなのですが、

(for i from 0 to 100 collect i)

だけでなく、

(collect i for i from 0 to 100)

(do (print (cons i (times 2 i))) for i from 0 to 10)
;=>
;(0 . 0)
;(1 . 2)
;(2 . 4)
;(3 . 6)
;(4 . 8)
;(5 . 10)
;(6 . 12)
;(7 . 14)
;(8 . 16)
;(9 . 18)
;(10 . 20)
;NIL
;

のようにキーワードをひっくり返して書いても良かったりします。

自分的には、CLのLOOPよりこっちの方が読み易くて好きだったりするのですが、このひっくり返した構文って最近流行りの内包表記っぽいですよね。

それで、その内包表記のCL版なのですが、やっぱり便利なので最近になって色々実装してみたりしている人が沢山いるようです。

等々

大体のものは、1991年に発表されたGuy Lapalme氏のImplementation of a “Lisp comprehension” macroという論文を参考にしていてCLのLOOPをマクロで包んだものが多いようです。

それで形としては、結局INTERLISPのFORループみたいなものができてしまっていて30年ぐるっと回って元に戻って来ちゃったという感が強く、感慨深いものがあります(笑)

おまけ

Common Lisp対Schemeで比較されてあれこれ言われる今日ですが、色々違いこそあれMITのMACLISP系の流れのもので所詮似た者同士に思えます。

XEROX系のINTERLISPは、ほぼ絶滅してしまいましたが、これらのMIT系とは大分違い、現在のLISP観からするとかなり逸脱したものになっているので、変ったものが好きな方には是非お勧めしたいです。*3

*1:Chinual 3rd ed. 1981

*2:ちなみに、INTERLISPのFORのMACLISPへの移植は1976年にされていたようです。

*3:例えば、LOOPマクロの英文として読み下せるような文法というのは、INTERLISPの一つの特長のように思えます。

2008-01-27

Getting Started in *LISP (3)

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

*LISPのチュートリアルをさらってみることの3回目。

1.2.3 Defining a More Complex Automaton

この節では、もうちょっと複雑なオートマトン、"9ライフ"を作成してみるとのこと。

ルールは以下の通り

  1. 各々のセルの周囲の0でないセルの個数を数える。
  2. 上記の個数が、1より小さいか、3より大きい場合、セルの値から1引く
  3. 同様に、2個から3個の場合は、セルの値に1足す

周囲の定義には2種類あり、フォン・ノイマン型とムーア型があるそうな。

フォン・ノイマン型は、上下左右の4方向が隣人とみなされ、ムーア型は、8方向が隣人。

今回は9ライフなので、ムーア型を採用。

逐次型と並列型の違い

各々のセルを一世代、更新する関数を逐次実行で書くとすると、x軸とy軸のアレイをループ処理することになり、下記のようになるんじゃないかとのこと。

(defun serial-one-step (array-x-size array-y-size)
  (dotimes (x array-x-size)
    (dotimes (y array-y-size)
      (let ((neighbors (list	(get-cell (- x 1) y)
				(get-cell (+ x 1) y)
				(get-cell x (- y 1))
				(get-cell x (+ y 1))
				... )))
	(store-new-cell-value x y
			      (new-value neighbors)))))
  (update-grid))

内容としては、隣のセルの番地を計算して、その値を取得し結果を合計している。

隣のセルの値の取得については、*LISPには隣のグリッドの値を並列に取得するためのnews!!という関数が用意されているので、ループを回す必要はない。

newsは東西南北のn、e、w、sの意味。

(news!! x y)となるので、(news!! 1 0)の場合、自身の東の値となる。

東の値を自身の位置に表示すると結果として西にずれたように見える。

(let ((g (random!! 10)))
  (ppp g :end '(8 8))
  (ppp (news!! g 1 0) :end '(8 8))
;=>
;     DIMENSION 0 (X)  ----->
;
;3 4 0 0 3 3 2 2 
;4 0 1 8 3 4 5 9 
;5 2 0 0 5 3 7 2 
;6 1 5 7 1 0 4 6 
;0 5 8 9 8 6 4 8 
;1 3 8 3 8 0 8 6 
;9 0 5 6 3 6 8 2 
;3 2 7 8 4 6 0 9 
;
;     DIMENSION 0 (X)  ----->
;
;4 0 0 3 3 2 2 2 
;0 1 8 3 4 5 9 1 
;2 0 0 5 3 7 2 6 
;1 5 7 1 0 4 6 6 
;5 8 9 8 6 4 8 1 
;3 8 3 8 0 8 6 1 
;0 5 6 3 6 8 2 2 
;2 7 8 4 6 0 9 6 

news!!を使うと、フォン・ノイマン型の隣人を求める関数は、

(defun neumann-count (grid)
  (+!! (news!! grid  0 -1)	;; north
       (news!! grid  0  1)	;; south
       (news!! grid -1  0)	;; west
       (news!! grid  1  0)	;; east
       ))

のように定義できる。

ムーア型は、8方向なので、

(defun moore-count (grid)
  (+!!	(news!! grid  0 -1)			;; north
	(news!! grid  0  1)			;; south
	(news!! grid -1  0)			;; west
	(news!! grid  1  0)			;; east
	(news!! grid -1 -1)			;; northwest
	(news!! grid -1  1)			;; southwest
	(news!! grid  1 -1)			;; northeast
	(news!! grid  1  1)			;; southeast
	))

となる。

周囲の非ゼロのセルの個数の合計は、ノイマン型とムーア型を*neighborhood*で切り分けられるように定義するとすれば、

(defvar *neighborhood* :neumann)

(defun neighbor-count ()
  (*let ((grid (signum!! *automata-grid*)))
    (ecase *neighborhood*
      (:moore (moore-count grid))
      (:neumann (neumann-count grid)))))

のように書ける。signum!!はsignumのSIMD命令版。*LETは、pvarを扱うlet

;; ランダムな値で埋めて、ノイマン方式で隣人の数を勘定してみる
(progn
  (random-grid)
  (view 8 8)
  (ppp (neighbor-count) :end '(8 8)))

;=>
;     DIMENSION 0 (X)  ----->
;セルの値
;8 8 4 0 0 8 5 6 
;5 8 4 9 0 1 7 2 
;5 7 2 4 9 7 6 3 
;9 4 9 5 5 4 0 2 
;1 2 4 7 5 3 1 6 
;8 1 1 6 7 8 5 8 
;4 5 8 4 8 3 8 7 
;8 7 1 4 1 5 0 2 
;
;     DIMENSION 0 (X)  ----->
;隣人数
;4 4 3 3 2 3 4 4 
;4 4 4 2 3 3 4 4 
;4 4 4 4 3 4 3 3 
;4 4 4 4 4 3 4 3 
;4 4 4 4 4 4 3 4 
;4 4 4 4 4 4 4 4 
;4 4 4 4 4 4 3 4 
;4 4 4 4 3 3 4 3 
;

2008-01-26

Getting Started in *LISP (2)

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

*LISPのチュートリアルをさらってみることの2回目。

1.2.2 Defining a Simple Automaton

この節では、簡単なオートマトンを作ってみるとのこと。

ルールは下記の通り

  1. 各々のセルは0〜9の値を持つ
  2. セルの偶数ならば、2で割る
  3. 奇数ならば、1足して2を掛ける

ということで、世代を進めるための関数を作ってみる。

(defun one-step ()
  (if!! (evenp!! *automata-grid*)
	(floor!! *automata-grid* 2)
	(*!! (1+!! *automata-grid*) 2)))
  • if!!

*LISPでは、各グリッドが各々のプロセッサに一対一で割り振られていて、それぞれ値を持っている。

この値に対して一斉にある操作をしたい場合(以下面倒なので、SIMD云々と略してみる)は、!!系の名前になっている。

if!!はSIMD版のif。上記、one-stepでは、evenp!!、floor!!、*!!、1+!!がSIMD命令。

色々と定義

;; 状態の数 (0〜9で10通り)
(defvar *total-number-of-states* 10)

;; グリッドに値を設定するが、*total-number-of-states*を法とした数に設定される 
(defun set-grid (newvalue)
  (*set *automata-grid*
	(mod!! newvalue *total-number-of-states*)))

(set-grid 27)

(view 8 3)
;     DIMENSION 0 (X)  ----->
;
;7 7 7 7 7 7 7 7 
;7 7 7 7 7 7 7 7 
;7 7 7 7 7 7 7 7 

;; 乱数を設定してみる。
(set-grid (random!! 27))
(view 8 3)
;     DIMENSION 0 (X)  ----->
;
;3 4 8 4 0 3 6 0 
;1 2 3 9 2 6 2 6 
;7 6 3 4 9 3 3 3 

;; 上記は便利なので、関数として定義
(defun random-grid ()
  (set-grid (random!! *total-number-of-states*)))

;; 何世代かをまとめて実行するユーティリティ
(defun run (&optional (n 1))
  (dotimes (i n)
    (set-grid (one-step))))
  • 実験してみる
(progn
  (random-grid) ; 乱数で埋める
  (view 8 8)
  (run 100)     ; 百世代実行
  (view 8 8))   ; 結果を確認
;=>
;     DIMENSION 0 (X)  ----->
;
;0 3 1 9 2 9 5 4 
;7 3 2 3 7 4 6 3 
;5 5 4 0 4 9 9 3 
;7 8 0 9 0 7 4 5 
;5 6 8 3 0 3 3 0 
;3 2 1 3 4 4 9 9 
;2 8 4 8 4 9 6 7 
;7 4 1 4 5 1 8 0 
;
;     DIMENSION 0 (X)  ----->
;
;0 1 4 0 1 0 2 2 
;4 1 1 1 4 2 2 1 
;2 2 2 0 2 0 0 1 
;4 4 0 0 0 4 2 2 
;2 2 4 1 0 1 1 0 
;1 1 4 1 2 2 0 0 
;1 4 2 4 2 0 2 4 
;4 2 4 2 2 4 4 0 

このパターンの場合、何世代か実行すると、0、1、2、4の数値に収束する。

うーん、勿論シミュレータなので、実際には、ループに展開されているだけなのですが、実機だと実際に65536個のプロセッサで同時に計算できたりするわけで、ロマンですねー。

並列処理で思い出したのですが、Dan Friedman氏の60歳を祝って著名な関係者が記念の講演をしていて、その中にGuy Steel氏の講演もあります。

これまでGLSが研究して来た内容が数々紹介されていて面白いのですが、並列の話もしていました。

この中の説明は、*LISPではなくてConnection Machine Lispの説明になっていますが、並列の話題に限らず、非常に面白い講演なのでおすすめです!

2008-01-25

Getting Started in *LISP (1)

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

*LISPのシミュレータに何か魅かれるものがあるので、とりあえずGetting Started in *LISPというチュートリアルを試してみることにしました。

Chapter 1. Instant *LISP

1章では、セル・オートマトンを作ってみるようです。

序盤で、セル・オートマトンの解説があって、次に*LISPの使用方法について。実機もシミュレータも、

(*lisp)
(*cold-boot :initial-dimensions '(16 16))

のような感じでスタートさせます。

  • 1.2 Using *Lisp
  • 1.2.1 Defining an Automata Grid

とりあえず、セル・オートマトンの実習開始

(*defvar *automata-grid* 0)
*AUTOMATA-GRID*

でグリッドを作成。初期値は、0

(ppp *automata-grid* :mode :grid :end '(8 5))
;=>
;     DIMENSION 0 (X)  ----->
;
;0 0 0 0 0 0 0 0 
;0 0 0 0 0 0 0 0 
;0 0 0 0 0 0 0 0 
;0 0 0 0 0 0 0 0 
;0 0 0 0 0 0 0 0 

pppは、pretty-print-pvarの略で、グリッドの内容を綺麗に整形して表示してくれます。

その前にpvarですが、parallel variableの略です。

操作に便利なユーティリティを色々と定義しつつ、関数を紹介

  • viewたん
(defun view (&optional (width 8) (height 5))
  (ppp *automata-grid* :mode :grid :end (list width height)))

グリッドを表示するためユーティリティ

  • read-cell
(defun read-cell (x y)
  (pref *automata-grid* (grid x y)))

(read-cell 5 1)
;=>0

一個のセルを表示するユーティリティ。

prefはpvarと、位置情報を引数に取って指定した位置の内容を返すもの。aref的なもの。

gridは、xとyから位置情報を生成する関数の模様。

  • set-cell
(defun set-cell (x y newvalue)
  (*setf (pref *automata-grid* (grid x y)) newvalue))

セルの内容を設定するユーティリティ

*setfは、pvarを操作するためのsetfのようなもので、感覚的にはsetf。

(set-cell 5 1 7)

(read-cell 5 1)
7

(view 8 3)
;=>
;     DIMENSION 0 (X)  ----->
;
;0 0 0 0 0 0 0 0 
;0 0 0 0 0 7 0 0 
;0 0 0 0 0 0 0 0 
  • set-grid
(defun set-grid (newvalue)
  (*set *automata-grid* newvalue))

グリッド全体の値を設定するユーティリティの模様。

*setは、setqのpvar版の模様。

(set-grid 7)

(view 8 3)
;
;     DIMENSION 0 (X)  ----->
;
;7 7 7 7 7 7 7 7 
;7 7 7 7 7 7 7 7 
;7 7 7 7 7 7 7 7 

;; 乱数で埋めてみる
(set-grid (random!! 10))

(view 8 3)
;     DIMENSION 0 (X)  ----->
;
;5 4 0 6 5 2 3 1 
;3 9 6 2 0 1 5 1 
;4 9 9 0 7 6 6 6 

random!!は、通常の(random x)の並列版で、返り値として全体のpvarを返すもの。

Practical Common Lisp (23)

| 15:24 | Practical Common Lisp (23) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (23) - わだばLisperになる

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

大分間が開いてしまいました。かなり、まったり進行です。

Mixing Different Parameter Types

  • 各種ラムダパラメータは組み合わせられるが、リストに現われる順番には決まりがある。下記の順番である必要がある。
    1. &optional
    2. &rest
    3. &key
  • これらのパラメータは組み合わせられるものの直感的でない動作をするものもあるので注意が必要
1.
((lambda (x &optional y &key z) (list x y z)) 1 2 :z 3)

2.
((lambda (x &optional y &key z) (list x y z)) 1 :z 3)
;=> error

3.
((lambda (x &optional y &rest z) (list x y z)) 1 3)
;=> (1 3 nil)

4.
((lambda (&rest rest &key x y z) (list rest x y z)) :x 1 :y 2 :z 3)
;=> ((:X 1 :Y 2 :Z 3) 1 2 3) 

個人的には、2のエラーは&keyと&restが同じ場所に格納されると考えると合点が行くんじゃないかと思っています。

2008-01-24

Vecto

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

今回は、Vectoを試してみます。

VectoはCL-VECTORSの簡便なインターフェースを提供するものだそうで、これを利用して簡単にベクター画像をpngファイルとして出力することができます。

パッケージ名vecto
作者Zach Beane氏
本拠地サイトVecto - Simple Vector Drawing with Common Lisp
ClikiCLiki: Vecto
ASDF-INSTALL

インストール

(asdf-install:install :vecto)一発で、ウェブからパッケージを取得しインストール完了です。

試してみる。

手頃な感じで、綺麗な画像が作れるので、これは重宝するかもしれません。

;; 動作
(defpackage #:vecto-examples (:use #:cl #:vecto))
(in-package #:vecto-examples)

(with-canvas (:width 400 :height 30)
  (let ((font (get-font "/usr/share/fonts/truetype/ipamona/ipag-mona.ttf"))
	(file "/var/tmp/vecto.png"))
    (set-rgb-fill 0.95 0.60 0.25)
    (rounded-rectangle 0 0 400 30 5 5)
    (fill-path)
    (set-rgb-fill 1.0 0.65 0.3)
    (rounded-rectangle 2.5 2.5 395 25 5 5)
    (fill-path)
    (set-font font 20)
    (set-rgb-fill 0 0 0)
    (draw-string 2.5 7.5 "1万円分のスイーツ、夢と散る(´・ω・`)")
    (save-png file)))
;
;
  • 出力画像


Dick Gabriel on Lisp

| 16:50 | Dick Gabriel on Lisp - わだばLisperになる を含むブックマーク はてなブックマーク - Dick Gabriel on Lisp - わだばLisperになる

昨日Redditを眺めていてみつけたんですが、Richard P. Gabriel氏のPodcastが公開されています。

Sunで働いてると思っていたんですが、いつの間にやらIBMで働いてたんですね。

早速聴いてみたんですが、LISPの歴史と特徴が語られる一時間。

英語が苦手な私は、あんまり聴き取れなかったんですが、LISPに関連することは一通り網羅されつつ、非常にうまく纏まっているので手短にLISP周辺のことを知るには最適じゃないかなと思いました。

2008-01-23

*LISPシミュレータ

| 19:40 | *LISPシミュレータ - わだばLisperになる を含むブックマーク はてなブックマーク - *LISPシミュレータ - わだばLisperになる

どう書くorgのお題:ライフゲームで*LISPのシミュレータを使ってみたわけなのですが、面白いので、もうちょっと追っ掛けてみることにしました。

とりあえず、毎回インストールの度にインストール方法を忘れてしまうので、メモがてら纏めてみたいと思います。

そもそも*LISPってなんだ

*LISPは、シンキングマシンズ社の65536個もプロセッサがある超並列計算機Connection Machineの上で動いていたLISPの処理系でした。

Wikipediaに詳しい説明とマニュアル等のリンクがあります。

Connection Machine用のLISPの処理系としては、当時この会社で働いていた、Guy L. Steel Jr.氏が筆頭となって、そのまんまな名前のConnection Machine Lisp(以下CM Lisp)というものが開発されていたようですが、結局完成はしなかったようで、代わりにこの*LISPが使われることになったようです。CM Lispについては、論文も書かれていますが、新しいアイディアが満載で、かなり強力だったようなので、完成して日の目を見なかったのは残念ですね。まあ、強力過ぎて完成しなかったのかもしれず…。ちなみにCM LispはCLtL2にもリーダマクロの説明等で、ちょこちょこ登場しています。

そんなこんなもあり、CM Lispと、*LISPは結構混同されていることが多いようです。

*LISPは、主にConnection Machineに合わせてマクロで拡張されたCLのスーパーセットになっているようです。また、現実的な需要からか、当時からこのシミュレータも存在してしました。1990年頃から公開されていた様子ですが、2003年に主要開発者のJ. P. Massar氏がANSI規格に沿うように改訂して今に至ります。

ソース入手

シミュレータのソースは下記からダウンロードできます。

インストール

自分が動作確認できた処理系は、AllegroとCLISPです。SBCL、Clozure CLはコンパイルに失敗してしまいます。(詳細後述)

まず、環境に合わせて設定ファイルを書き換え、コンパイルを実行しますが、処理系によっては、若干ソースを修正する必要があります。

  • ANSIに合わせたとか言いつつ、パッケージ名をuserと記載しているままな個所があるので、cl-user等に直します。

パターンとしては、以下の3つです。

user:: -> cl-user::
:user -> :cl-user
"USER" -> "CL-USER"
  • 設定ファイルにソースが展開されたパスを記述します。

ちなみに、unzipをするとディレクトリを作らず、撒き散らかすタイプです。

/var/tmp/starlispにディレクトリを作成して中身を展開したと仮定すると下記のような記述になります。

; make.lisp
(defparameter *defsystem-search-path* '("/var/tmp/starlisp/"))
;
; f20.sys
(define-alias "STARLISP" "/var/tmp/starlisp/")
(define-alias "VERSION" "/")
;
  • ファイル名の修正

f20.sysというファイルをF20.sysという名前に変更するか、リンクします。

大文字にしないと(ms :f20)したときにファイルの呼び出しに失敗します。

もしくは、f20.sysの中の、 :f20〜の個所を:|f20|として、(ms :|f20|)とすれば大丈夫ですが、こっちの方が面倒かと思います。

;
(compile-file "/var/tmp/starlisp/make")
(load *)
(ms :f20)
;〜
;

これで、システムが読み込まれます。

;
(*lisp)
;

とすると、

Thinking Machines Starlisp Simulator.  Version 20.0

と出たりするので、おおっ!とか思いますが、単にパッケージを移動しているだけだったりします。

マニュアル

本格的なマニュアルは、*Lisp Reference Manualというものがあったようなのですが、当然ながら入手困難なようです。

配布物のなかに、tutorial.pdfと、getting-started-guide.txtというチュートリアルがあります。

自分は、PDFで見た目も綺麗ということで、tutorial.pdfを参照してgetting-started-guide.txtは見てなかったのですが、このエントリを書くにあたって、Wikipediaのリンクから辿って、マニュアルを漁っていたところ、getting-started-guideのPDF版を見付けました。

これを眺めたところ、ライフゲームの図が沢山あるので、チュートリアルの題材が思いっ切り今回のどう書くorgのお題のライフゲームだということに気付きました。

当然ながら、コードは洗練されています。思いっきりループ版を投稿したり、その後、このチュートリアルの劣化版のような並列構文版を投稿したりした自分が恥ずかしい…(;´Д`)

…まあ、良いや、誰も気付かないだろうし…。というか改めて確認しましたが、テキスト版のgetting-started-guide.txtよりtutorial.pdfの方が題材にしている処理系のバージョンが古いみたいですので、getting-started-guide.txtがお勧めです。

追記

  • SBCLとか、Clozure CLでのコンパイルが失敗する場所

type-system-deftypes.lispの

(deftype pvar (&optional (element-type '*))
  ;; I have to return a satisfies type with a closure so that typep can work.
  ;; But, returning a closure will blow up both subtypep and the compiler on lucid.
  (let ((closure (*lisp-i::pvar-type-predicate 
                  (cadr (*lisp-i::canonical-pvar-type `(pvar ,element-type))))))
    `(satisfies ,closure)))

;

の個所がコンパイルできないようです。SBCLや、Closure CLでは、SATISFIESにLAMBDA式が渡るのが許されないことが原因のようなのですが、自分のレベルでは、対処方法が分からず…。ここを通過できれば、使えるんですが…。アドバイス等ありましたら是非お願いしたいです!

2008-01-22

お題(126): ライフゲーム

| 19:21 | お題(126): ライフゲーム - わだばLisperになる を含むブックマーク はてなブックマーク - お題(126): ライフゲーム - わだばLisperになる

一人でArc祭りをしていたため、どう書くorgのお題をスルーしておりました。

もちろん、ぱっと見で簡単なら、スルーしないんですが、ライフゲームってのがAIっぽくて、いやに難しそうだったので、まあ今回もスルーで良いかなと思っていたんですが、Wikipediaの解説を読んだら、ライフゲームが成立するルールは想像していたより簡単だったので、挑戦することにしました。

以前から升目を見る度に、*LISPが使えるんじゃないかと思ってはいたのですが、今回初めて*LISP(シミュレータ)で書いてみました。

*LISPは、Connection Machine用に開発されたこともあり、並列処理のための構文が沢山あります。

画像処理等も、1ピクセルを1プロセッサに割り振って、一回で処理したりするらしいです。

しかし、附属のチュートリアルを読んでも、全然理解できず、一発で並列処理させる構文も見付けられなかったので、無念ながら普通にループで書きました。

自分のイメージとしては、

(defun get-next!! (〜)
  "セルが次に生きているかを判定する関数"
  〜)

(get-next!! *cell*)
とか
(map!! (lambda (x) (get-env!! x) *cell*) ...etc

みたいな感じで、グリッドを丸ごと一発で処理できる筈だと思ってはいるんですが…。(!!は、グリッド全体を一気に処理する関数に付く目印)

*LISPのインストールについては、割と手間が掛って面倒臭く、解説も長くなりそうなので別エントリに書いてみたいと思います。

俺Arc祭り 2008冬 (8)

| 08:10 | 俺Arc祭り 2008冬 (8) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (8) - わだばLisperになる

やっと最後まで辿り着きました、俺Arc祭りこと、生後3週間目のArc追っかけ。

最後に来て、ラムダパラメータリストについてです。

keyの代わりに、db(ハッシュ)を使用することにし、また、分割代入をサポートするとのこと。

キーワードを一々指定するのは面倒だから、ハッシュテーブルを引数として食べさせるってことでしょうか。

便利なような便利でないような…。

ノープランで頭から作っていただけに、let、with、def、macro(mac)は定義し直し。

また、変数の分割代入ですが、混み入ってくると正しい文法なのかどうか怪しいです。

;; 動作
(def foo (x (ds (i j)) (get m n) (opt q 'a) . z)
  (list x i j m n q z))

(foo 1 '(red green) (db m 'a n 'b) 'hel 'lo)
;-> (1 RED GREEN A B HEL (LO))

(let x 5
     x)
;-> 5

(with (x 5 y 6)
  (list x y))
;-> (5 6)

(let (ds (x y (z))) '(1 2 (3))
     (list x y z))
;-> 1 2 3

(with ((ds (x y z)) '(1 2 3) a 5)
  (list x y z a))
;-> 1 2 3 5

;; これはアリなのだろうか?
(let (a . b) '(1 2 3 4)
     (list a b))
;->(1 (2 3 4))

;; これで良いのか?
(let (ds ((a b) . rest)) '((1 2) 3 4)
     (list a b rest))
;->(1 2 (3 4))

(with ((ds ((a b) . rest)) '((1 2) 3 4)
       x 10)
  (list a b rest x))
;->(1 2 (3 4) 10)

;; おれおれ定義
(cl:defmacro let (var val cl:&body body)
  `(cl:destructuring-bind ,(remove-ds 
			    (opt-to-&optional
			     (dotex-to-&rest `(,var))))
       (list ,val)
     (declare (ignorable ,@(metatilities:flatten (remove-ds `(,var)))))
     ,@body))

(cl:defmacro with (spec &body body)
  (reduce (fn (x res) `(let ,@x ,res))
	  (loop :for i :on spec :by #'cddr 
	        :collect (metatilities:firstn 2 i))
	  :initial-value `(progn ,@body)
	  :from-end 'T))

(cl:defmacro def (name args cl:&body body)
  (multiple-value-bind (spec /ds /syms) 
      (replace-specs (opt-to-&optional (dotex-to-&rest args)))
    (if /ds
	`(cl:defun ,name ,spec
	   (destructuring-bind ,/ds ,/syms
	     ,@body))
	`(cl:defun ,name ,spec
	   ,@body))))

;; 他のエッセイを読んだら、macroじゃなくて、macになってたのでついでに変更してみる
(cl:defmacro mac (name args cl:&body body)
  (multiple-value-bind (spec /ds /syms) 
      (replace-specs (opt-to-&optional (dotex-to-&rest args)))
    (if /ds
	`(cl:defmacro ,name ,spec
	   (destructuring-bind ,/ds ,/syms
	     ,@body))
	`(cl:defmacro ,name ,spec
	   ,@body))))

;; ラムダパラメータ分解ユーティリティ
(cl:defun opt-to-&optional (expr)
  (loop :for x :in expr
        :nconc (if (eq 'opt (metatilities:car-safe x))
		   `(&optional ,(if (cl:= 2 (length x))
				    (cadr x)
				    (cdr x)))
		   (list x))))

(cl:defun dotex-to-&rest (expr)
  (cl:cond ((atom expr) `(&rest ,expr))
	   ((tailp () expr) expr)
	   ('T (cl:let ((x (copy-list expr)))
		 (rplacd (last x) (list '&rest (cdr (last x))))
		 x))))

(cl:defun replace-specs (expr)
  (loop :with ds :and vars
     :for x :in expr
     :collect (cl:cond ((eq 'ds (metatilities:car-safe x))
			(cl:let ((sym (gensym "DS-")))
			  (push sym vars)
			  (push (cadr x) ds)
			  sym))
		       ((eq 'get (metatilities:car-safe x))
			(cl:let ((sym (gensym "DB-")))
			  (push (cdr x) ds)
			  (push `(list ,@(mapcar (cl:lambda (x) `(get ,x ,sym)) (cdr x))) vars)
			  sym))
		       ('T x))
     :into specs
     :finally (return (values specs ds `(list ,@vars)))))

(defun remove-ds (expr)
  (loop :for x :in expr
        :collect (if (eq 'ds (metatilities:car-safe x))
		     (cadr x)
		     x)))

俺Arc祭り 2008冬 (7)

| 02:46 | 俺Arc祭り 2008冬 (7) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (7) - わだばLisperになる

もう少しで終了の俺Arc祭り。知らぬ間に世の中では、俺Scheme/Lisp祭りが始まっている様子。

今年、Schemeは盛り上がりそうだなー。

Common Lispも、意味なく盛り上がんないかな。

Common Lisp面白いと思うんだけどなあ。

それはさておき、

16. Overloading

クラスを作るときに関数を指定して実行時に指定した関数をオーバーロードするとのことですが、ギブアップです(;´Д`)

 (= pt (class nil 'x 0 'y 0 pr my-pr))

とかすると、ptの呼び出しでは、prじゃなくて、my-prが呼び出される、ということでしょうか。

どうすれば良いのか検討もつかないなあ。

17. DBs are hashes/alists

dbというものが定義されて、これは、連想リストや、ハッシュ的なものだそうです。

  • newdb、db、get

newdbで新規のdbを作成、dbは簡略版で、問い合わせのテストにeqを過程するものだそうです。

getで、キーを指定して値を取り出します。

また、問い合わせに失敗した場合は、大域変数*fail*を返すとのこと。

;;
;; 動作
;(newdb eq 'x 'a 'y 'b)

(= foo (db x 'a y 'b))

(get x foo)
;-> a

(each x (db x 1 y 2)
   (pr x)
   (keep key))
;12
;(X Y)

;; おれおれ定義
(cl:defmacro newdb (test &rest keys-&-vals)
  `(loop :with ht = (make-hash-table :test #',test)
         :for kv :on ',keys-&-vals :by #'cddr
         :do (setf (cl:gethash (car kv) ht) (%unquote (cadr kv)))
         :finally (return ht)))

(cl:defmacro db (&rest keys-&-vals)
  `(newdb eq ,@keys-&-vals))

(shadow 'get)

(defparameter *fail* nil)

(cl:defmacro get (key db)
  `(multiple-value-bind (val test) (cl:gethash ',key ,db)
     (cl:if test val '*fail*)))

;; dbを扱えるようにeachを拡張。禁斷のeval発動…。
(macro each body
  (if (hash-table-p (eval (cadr body)))
      `(with-keep-or-sum 
	 (each/hash ,@body))
      `(with-keep-or-sum 
	 (each1 ,@body))))

(cl:defun %keys+values (ht)
  (loop :for k :being :the :hash-keys :in ht :using (:hash-value v)
        :collect k :into ks
        :collect v :into vs
        :finally (return (values (coerce ks 'vector) (coerce vs 'vector)))))

(cl:defmacro each/hash (var ht cl:&body body)
  (with (/v (gensym) /k (gensym) /cnt (gensym))
    `(multiple-value-bind (,/k ,/v) (%keys+values ,ht)
       (cl:let (,var key)
	 (declare (ignorable key ,var))
	 (to1 ,/cnt (length ,/k)
	   (setq ,var (aref ,/v ,/cnt) key (aref ,/k ,/cnt))
	   ,@body)))))

;; with-keep-or-sumの定義が変だったので変更
(cl:defmacro with-keep-or-sum (&body body)
  (with (s (x-finder 'sum body) k (x-finder 'keep body))
    (cl:cond ((and s k) (error "SUMとKEEPはどちらかでお願いしたい。"))
	     (s `(with-sum
		   ,@body))
	     (k `(with-keep
		   ,@body))
	     ('T `(progn ,@body)))))

2008-01-20

俺Arc祭り 2008冬 (6)

| 20:35 | 俺Arc祭り 2008冬 (6) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (6) - わだばLisperになる

惰性で続けております、俺Arc祭り。気力が無くなってまいりました。

15. Classes and Objects

クラスとオブジェクトです。単一継承にする予定とのこと。

あんまり詳しく説明はされてません。

基本的に名前の付け替えで逃げました。(++ (p1 'x))というのは逃げきれませんでした。

意味的には(incf (slot-value p1 'x))ということだと思うんですが…。

切ったり貼ったりの無理矢理風味に出来上がりました。

;;
;; 動作
(= pt (class nil 'x 0 'y 0)) ;ptというクラスを作る?

(type pt (x 0) (y 0)) ; 上記の簡便な方法?

(= p1 (new pt))    ;インスタンスをnewで作ってp1に代入

(p1 'y)    ; p1は自動的にメソッドの名前にもなり、スロットを読み出せる。
;=> 0

(++ (p1 'x)) ;読み出して、値をセット
;=> 1

;; おれおれ定義
(cl:defun %unquote (sym)
  (if (and (consp sym) (eq 'quote (car sym)))
      (cadr sym)
      sym))

(shadow 'class)
(cl:defmacro class (name &body body)
  `(cl:defclass ,(if name name (gensym)) ()
     ,(loop :for l :on body :by #'cddr 
	    :collect `(,(%unquote (car l)) :initform ,(cadr l)))))

;; classと、newのために拡張
(cl:defmacro = (place val)
  (cl:cond ((and (consp val) (eq 'class (car val)))
	    `(progn
	       (cl:setf ,place (class ,place ,@(cddr val)))
	       (defmethod ,place (slot)
		 (slot-value ,place slot))))
	   ((and (consp val) (eq 'new (car val)))
	    `(progn
	       (cl:setf ,place ,val)
	       (defmethod ,place (slot)
		 (slot-value ,place slot))))
	   ('T `(cl:setf ,place ,val))))

(shadow 'type)
(cl:defmacro type (name &body body)
  `(cl:defclass ,name ()
     ,(mapcar (cl:lambda (x) `(,(car x) :initform ,(cadr x)))
	      body)))

(cl:defmacro new (class)
  `(make-instance ',class))

2008-01-19

cl-serializer

| 05:31 | cl-serializer - わだばLisperになる を含むブックマーク はてなブックマーク - cl-serializer - わだばLisperになる

今回は、cl-serializerを試してみます。

シリアライズって何ですか?という私なのですが、調べてみたところ、処理系内部のオブジェクトデータの外部へ入出力するためのものなんですね。

パッケージ名cl-serializer
本拠地サイトcl-serializer
ClikiCLiki: cl-serializer
ASDF-INSTALL

インストール

asdfに対応はしているのですが、依存するパッケージがことごとく、Clikiに登録されておらず、darcで引っ張ってこないといけません。これが、結構手間です。

自分は、darcで引っ張ってきて、tarボールにまとめ、asdf-installをしました。


$ cd /var/tmp
$ darcs get http://common-lisp.net/~loliveira/darcs/babel                   
$ darcs get --partial http://common-lisp.net/project/cl-def/darcs/cl-def    
$ darcs get http://common-lisp.net/project/stefil/darcs/stefil              
$ darcs get http://common-lisp.net/project/defclass-star/darcs/defclass-star
$ darcs get http://common-lisp.net/project/cl-serializer/darcs/cl-serializer

$ tar zcvf ....

(asdf-install:install "http://cffi-net.accela.net/releases/cffi-grovel-r2.tgz")
(asdf-install:install "/var/tmp/babel.tar.gz")
(asdf-install:install "/var/tmp/cl-def.tar.gz")
(asdf-install:install "/var/tmp/stefil.tar.gz")
(asdf-install:install "/var/tmp/defclass-star.tar.gz")
(asdf-install:install "/var/tmp/cl-serializer.tar.gz")

試してみる


(require :cl-serializer)

;; データ
(defvar obj '(a (b c) a b (d e (f g (h i)))))

;; 書き出し
(let ((sr (cl-serializer:serialize obj)))
  (with-open-file (out "/var/tmp/ser.dat" :direction :output :if-exists :supersede
		       :element-type 'unsigned-byte)
    (series:collect-stream out (series:scan sr) #'write-byte)))

;; 読み込み
(with-open-file (in "/var/tmp/ser.dat" :element-type 'unsigned-byte)
  (pprint (cl-serializer:deserialize (coerce (series:collect (series:scan-stream in #'read-byte)) 'vector))))
;=> (A (B C) A B (D E (F G (H I))))

serializeでシリアライズして、deserializeで戻し。

なるほど、なるほど。

私には用途が思い付きませんが、きっと凄いものなのでしょう(´▽`*)

俺Arc祭り 2008冬 (5)

| 03:05 | 俺Arc祭り 2008冬 (5) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (5) - わだばLisperになる

12. Data Types

データの型について

  1. シンボル
  2. 数(Common Lispと同じ)
  3. コンス
  4. 文字
  5. 文字列
  6. 配列
  7. クラス、オブジェクト
  8. データベース(ハッシュ/連想リスト)
  9. 関数
  10. マクロ
  11. その他

だそうです。マクロってのが光ってはいますね。

13. Compounds = Functions on Indices

複合したデータをインデックス付きの関数とみなす試みとのこと。

これも無理目なので、funcallみたいな、obcallというものをでっち上げて代用することにしてみました。


;; 動作
(obcall "hello" 2) 
;=> #\l

(obcall '(foo bar baz) 1)
;=> bar

(map #'pr '(3 4 1 2))
;=> 3412

(map "carpet" '(3 4 1 2))
;=> (#\p #\e #\a #\r)

;; ---- 定義 
(defun obcall (obj index)
  (elt obj index))

(defun map (fn &rest args)
  (if (functionp fn)
      (apply #'cl:mapcar fn args)
      (apply #'cl:mapcar (fn (x) (obcall fn x)) args))) ;複数の引数の場合は?

14. Strings Work Like Lists

文字列をリストに見立てるとのこと。TAO/ELISって文字列をリストとして扱えたらしいというのをどっかで読んだ記憶があるのですが、こういうこともできたんでしょうか。マニュアルには載ってないので、記憶違いかもしれませんが…。

色々と夢が広がりまくりなのですが、適当にできそうなものだけ作ってみました。

bol

俺Arc祭り 2008冬 (4)

| 02:22 | 俺Arc祭り 2008冬 (4) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (4) - わだばLisperになる

Lisp系言語には繰り返し構文が色々ありすぎる位ですが、Arcでも新しい構文を導入するようです。

10. Iteration

  • for, each, to, while

Common LispのDOはわかりづらい!とのこと。自分は、DO大好きなので、全然そう思わないのですが、多分少数派なんでしょう。そういう意見しか目にしたことないし…。繰り返し機構が付いたLETだと思えば、そんなに難解でもないと思うんですが、どうなんでしょう。あと、LOOPは色々話題にのぼるんですが、DOって話題になるとしても「気持ち悪い」で終わることが多いですね(笑)

さっと見た感じでは、ArcではCというかPerlの機構を取り入れてみたようです。どうもこの時のArcは、思い切りPerlの方向に進んでいるような。すべての言語はLispに向かうんじゃなかったのか!

11. Iteration Captures

繰り返し時にsumとか、keepとかitに値を束縛するという試み。加えてwhileは、itを束縛するとのこと。

loopのsum、collect、Perlの、$_とか、そういう感じでしょうか。

keepはリストに蓄え、sumは数を合計します。どうしてかは知りませんが、keepと、sumは二者択一だそうです。

下記のコードはCommon Lispと俺Arcの組み合わせで記述していることもあいまって非常にごちゃごちゃしています。

しかし、どうも括弧の足りないcondは好きになれないな…。


;; 動作
(for (= i 0) (< i 10) (++ i)
  (pr i))

;-> 123456789
NIL

(each x '(1 2 3 4 5) 
      (pr x)
      (sum x))

(each x '("al" "bob" "joe")
   (if (> (len x) 2) (keep x)))
;=> ("bob" "joe")

(to x 5
  (sum x)
  (pr x))
;->01234
10

(let i 0
   (pr (while (< (++ i) 10) (pr i) (keep i)))))
;123456789(1 2 3 4 5 6 7 8 9)

;; 定義

;; for
;;predが受け付けるのは、任意の式なのか、決まった形式か分からないので、
;;predの変数多重評価問題放置
(macro for (init pred then . body) 
  (with (tag (gensym))
    `(do ,init
	 (block nil
	   (tagbody 
	      ,tag
	      (unless ,pred
		(return))
	      ,@body
	      ,then
	      (go ,tag))))))

;; ++
(shadow 'incf)
(shadow '++)
(macro ++ body
  `(cl:incf ,.body))

;; to
(macro to body
  `(with-keep-or-sum to1
     ,.body))

(macro to1 (var limit . body)
  (with (/limit (gensym) /tag (gensym))
    `(let ,/limit ,limit
	  (do (= ,var 0)
	      (block nil
		(tagbody 
		   ,/tag
		   (unless (< ,var ,/limit)
		     (return))
		   ,@body
		   (++ ,var)
		   (go ,/tag)))))))

;; each
(macro each body
  `(with-keep-or-sum each1
     ,@body))

(macro each1 (var obj . body)
  (with (/i (gensym) /obj (gensym))
    `(with (,/obj (coerce ,obj 'vector) ,var nil)
       (to1 ,/i (length ,/obj)
	 (setq ,var (aref ,/obj ,/i))
	 ,@body))))

;; while
(macro while body
  `(with-keep-or-sum while1
     ,.body))

(macro while1 (pred . body)
  (let tag (gensym)
       `(block nil
	  (tagbody
	     ,tag
	     (if ,pred
		 (do ,.body
		     (go ,tag))
		 nil)))))

(macro with-keep-or-sum (fn . body)
  (with (s (x-finder 'sum body) k (x-finder 'keep body))
    (cond (and s k) (error "SUMとKEEPはどちらかでお願いしたい。")
	  s `(with-sum
	       (,fn ,.body))
	  k `(with-keep
	       (,fn ,.body))
	  `(,fn ,.body))))

(macro with-keep body
  (let /tem (gensym)
       `(let keep (list ())
	     (declare (ignorable keep))
	     (let ,/tem keep
		  (cl:macrolet ((keep (var) 
				  `(rplacd (cl:last ,',/tem) (list ,var))))
		    ,@body))
	     (cl:cdr keep))))

(macro with-sum body
  `(let sum 0
     (declare (ignorable sum))
     (cl:macrolet ((sum (var) `(++ sum ,var)))
       ,@body)
     sum))


俺Arc祭り 2008冬 (3)

| 00:55 | 俺Arc祭り 2008冬 (3) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (3) - わだばLisperになる

だんだん疲れて来てしまいました。俺Arc祭り。段々恥ずかしい駄目駄目なコードを晒すのも恥ずかしくなってまいりました。

実際のところは駄目なところが分からないのと、恥ずかしいところが分からない自分が恥かしいのですが。

それはさておき、

9. Binding

  • with, let

Arcの変数束縛機構ですが、letは、変数を一組しかとらないことにするみたいです。Gaucheのlet1と同じ感じ。

複数の場合には、withを使用するとのこと。

また、(let x 3 (foo x))は((fn (x) (foo x) ) 3)に展開されるんだそうです。letがlambdaに展開されるってことでしょうか。

色々深いんだと思いますが、単にletに展開するだけにしました。


;; 動作
(with (x 'a y 'b)
  (list x y))
;=> (A B)

(let x 'a
     (cons x 5))
;=> (A . 5)

;; 定義
(cl:defmacro with ((&rest spec) &body body)
  `(cl:let ,(cl:do ((s spec (cddr s)) res)
		   ((endp s) (nreverse res))
		   (push `(,(car s) ,(cadr s)) res))
	   ,@body))

(shadow 'let)
(cl:defmacro let (var val &body body)
  `(cl:let ((,var ,val))
	   ,@body))

俺Arc祭り 2008冬 (2)

| 00:18 | 俺Arc祭り 2008冬 (2) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (2) - わだばLisperになる

だらだら続いております。俺Arc祭り。

自分の書いているものが、非常に読み辛く、また書いてても良く分からなくなって来たので、小分けにして行くことにしました。

8. Functions and Macro

  • fn

lambdaは、fnと書くそうです。


(macro fn body
  `(cl:lambda ,@body))

非常に安直に…。(funcall (fn (x) (+ x 3)) 3)としないと動きません…。

((fn (x) (+ x 3) ) 3)みたいにして動くようにする簡単な方法ってあるんでしょうか。

  • rfn

labels(再帰可能なローカル関数定義)は、rfnと書くとのこと。

rfnは、多分トップレベルでも使えるんだろうとは思いますが、色々大変そうなので、doに埋め込むことにしました。

段々と定義するのにパッケージを指定するのが面倒になってきたので、my-arcパッケージを定義するために、my-arc-defというパッケージを作成し、そこからインポートすることにしてみます。

  • no

doの例で出てきたので、nullの一般化されたものと勝手に解釈して適当に定義。

st
  • マクロはファーストクラスオブジェクト

局所マクロを作るのは単に変数に束縛するだけ。

これは無理なのでスルー。

しかし、マクロがファーストクラスオブジェクトだとどういう風にプログラミングスタイルが変わるんでしょうね。

例示されているmacroの例なんですが、


(macro (test . body)
  `(if ,test (do ,.body))) 

;; when?
(macro when (test . body)
  `(if ,test (do ,.body)))

;; 動作
(when 33
  'foo 'bar 'baz it)
;=> 33

これってタイポでwhenが抜けてるんですかね? whenだと合点が行くのですが…。

ひたすら続きます…。

2008-01-18

俺Arc祭り 2008冬 (1)

| 10:17 | 俺Arc祭り 2008冬 (1) - わだばLisperになる を含むブックマーク はてなブックマーク - 俺Arc祭り 2008冬 (1) - わだばLisperになる

ポール・グレアム氏のArcが、この冬に公開されるらしいとのこと。

それでそのArc公開のニュースなんですが、存外、話題にもなってない様子です。

もっとドッカンドッカン騒がれるのかと思ったんですが…。

Arcの計画が世に現われたのは、2001年の11月位とのことなので、早6年。

話の流れ的には、全くつながっていないのですが、この6年前のアイディアを、そのままCommon Lispのマクロで書いて俺Arcを作ってみることにしました。

多分、結構試してみた方は結構いるんじゃないかと思うんですが、へんてこ俺Arcを作って実物のArcに思いを馳せることができるの残り僅かかも知れません。

また、俺Arcを作ってみることで、本物のArcへの理解も深まるかもしれません。

Arcはまさに今が旬なのです!

ということで、早速、ノープランでこの2001年の発表を頭から順番に作っていってみます。

下準備

とはいえなんとなく必要そうなものは予め作ってみます。


(defpackage :my-arc
  (:use :cl))

(in-package :my-arc)

(cl:defmacro macro (cl:&body body)
  (cl:if (cl:consp (second body))
	 `(cl:defmacro ,@body)
	 `(cl:defmacro ,(first body) (cl:&body ,(second body)) ,@(cddr body))))

(cl:defmacro def (cl:&body body)
  (cl:if (cl:consp (second body))
	 (cl:if (tailp body ())
		`(cl:defun ,@body)
		`(cl:defun ,(first body) ,(%add-rest (second body)) ,@(cddr body)))
	 `(cl:defun ,(first body) (cl:&rest ,(second body)) ,@(cddr body))))

(cl:defun %add-rest (expr)
  (cl:let ((l (copy-list expr)))
     (cl:let ((tail (last l)))
	     (rplacd tail `(&rest ,(cdr tail)))
	     l)))

パッケージ名は安直に、my-arc。defmacroと、defunに、macro、defという名前を付け直してみました。&restパラメータは取らないそうなので、適当にSchemeのlambdaみたいにすることにしました。割と悲しげに仕上がりました。

4. Other Principles

Arcはポリモーフィックだそうで、+で文字列の連結とかするそうです。総称関数にしようかとも思いましたが、etypecaseで分けました。これは結構やってる人は多そうです。


;; 動作
(+ "foo" "bar")
;=>"foobar"

(pr (+ #(foo) "bar"))
;#(FOO b a r)
(+ '(foo bar) '(baz))
;(foo bar baz)

(+ 0 pi)
;3.141592653589793d0

;; 定義
(def + (arg . args)
  (etypecase arg
    (string (apply #'concatenate 'string arg args))
    (vector (apply #'concatenate 'vector arg args))
    (list (apply #'concatenate 'list arg args))
    (number (apply #'cl:+ arg args))))

5. Syntax

Arcでは、文法を定義して、foo.barのような呼び出しを可能にしてみる、とのことですが、これは当然ながらしんどいので中途半端に挑戦して諦めることにしました。

fn.y => (fn y)

fn:y => (fn 'y)

[+ _ 1] => (fn (x) (+ x 1))

だそうです。

角カッコのやつは、リーダマクロでできそうです。


;; 存在しないものを捏造
(macro arcall (expr)
  (arc-to-cl expr))

;; 定義
(let ((foo -33.5))
   (arcall truncate.abs.foo))
;=> 33, 0.5

(defun arc-to-cl (expr)
  (reduce #'list
	  (map #'read-from-string (ppcre:split "\\." (string expr)))
	  :from-end 'T))

6. Arc Core

condの括弧が多いので、減らすそうです。

自分はlet、do、condの括弧は割と苦にならないタイプなんですが、そういうのは少数派なんでしょうか…。(とはいえ、もう一段階ネストした、Schemeのmatch-letはわけがわかりませんが…。)

とりあえず、安直にボディを適当に振り分けて、itを使うためにkmrclのacondに展開することにしてみました。

ちなみにここで、Lisp 1.5のcondの暗黙のprognについて語られてますが、エミュレータのLisp 1.5で試した限りでは、Lisp 1.5のcondは暗黙のprognじゃないみたいなんですよね。lambdaのボディも暗黙のprognじゃないみたいで、Lisp 1.5は謎が多いです…。

それと、nilへのcar、cdrの適用は、エラーとのこと。


;; 動作
(cond (probe-file "/tmp/") (do (pr "it -> ") (pr it) (terpri))
      nil)
;it -> /tmp/

;; 定義
(shadow 'cond)

(macro cond body
  (cl:let ((cond-body (cl:do ((b body (cl:cddr b))
			      res)
			     ((endp b) (nreverse res))
			(cl:if (cl:cdr b)
			       (push `(,(cl:car b) ,(cl:cadr b)) res)
			       (push `(t ,(cl:car b)) res)))))
	  `(kmrcl:acond ,@cond-body)))

(shadow 'car)
(shadow 'cdr)

(defmethod car ((obj null))
  (error "The value ~S is not of type LIST." obj))

(defmethod car ((obj cons))
  (cl:car obj))

(defmethod cdr ((obj null))
  (error "The value ~S is not of type LIST." obj))

(defmethod cdr ((obj cons))
  (cl:cdr obj))

7. Assignment (Scope)

ローカル変数は値を代入すると暗黙に作られて、まだ宣言されていない変数に値を代入すると、現在のブロックの残りの部分までを有効範囲とする局所変数が作られるとのこと。

ブロックは、主にdoで作成。

=は、setfに相当するとのこと。

ArcdoはCommon LispのprognでprはCommon Lisp のprincだそうです。

変数を束縛しないのは、justdoになるとのこと。ってことは、do = prognなくて、justdo = prognなんでしょうか。

とりあえず、無理矢理letに変換することにしました。


(do (= x 5)
    (cons x 'a))
;=:> (5 . A)

(do (= x 5)
  (do (= y 6)
      (list x y)))
;=> (5 6)

;; 定義
(shadow 'do)

(macro do body
  (cl:let ((vars (%x-finder '= body)))
	  `(cl:let ,vars
		   (declare (ignorable ,@vars))
		   ,@body)))

(cl:defun %x-finder (sym form &optional taglist)
  (and form
       (if (eq sym (car form))
	   (push (cadr form) taglist)
	   (dolist (c (remove-if-not #'consp form) 
		    (delete-duplicates taglist))
	     (cl:let ((tem (%x-finder sym c taglist)))
		     (cl:when tem
		       (setq taglist tem)))))))

(shadow '=)

(macro = args
  `(cl:setf ,@args))

(macro justdo body
  `(progn ,@body))

(shadow 'princ)

(setf (symbol-function 'pr) #'cl:princ)

大した内容でもないのに長くなってしまいました。まだまだあるので、続きは別エントリにします…。

2008-01-17

お題: 除算・余剰を使わずに閏年 etc

| 01:25 | お題: 除算・余剰を使わずに閏年 etc - わだばLisperになる を含むブックマーク はてなブックマーク - お題: 除算・余剰を使わずに閏年 etc - わだばLisperになる

お題(123): 必ず解ける迷路

問題を見てすぐに諦めてしまいました(笑)

こういう問題の解法思い付くようになりたいとは思えど…。

お題(124): 除算・余剰を使わずに閏年

割り算を使わないということで、要素数4、100、400の循環リストを作って組み合わせるという方法でアプローチ。時間帯が早朝で誰もいなかったので、1番乗りだ!と思ったら2番目でした…。

なんとなくFizzBuzzっぽい問題な気もしないでもないです。

2008-01-16

Lispの変な関数名

| 01:07 | Lispの変な関数名 - わだばLisperになる を含むブックマーク はてなブックマーク - Lispの変な関数名 - わだばLisperになる

自分の好きな話題なのでトラックバックを打たせてもらいます!

確かに、古来のLispから引き継がれた変な名前の関数名は結構ありますよね。

そこで自分の分かる範囲で調べてみたことを書いてみたいと思います。

  • SETQのQ

これは、(set (quote foo))が縮まったもののようです。

qで終る系統は、quoteの略と、eqのqの系統があるようでeqの系統は、Common Lispでは滅びましたが、SRFI-1では復活したりしています。memqとか。

また、INTERLISP系にはquoteのq系統が沢山あるのですが、Maclisp系統にはそんなにないようです。

  • SETのF

これは、Kent Pitman氏によれば、"set field"の略だそうです。

onjoさんのLispy Days経由、ClikiのKent Pitman氏のcomp.lang.lispのポストであらましが読めます。

また、The Evolution of Lispによれば、これはPeter Deutsch氏のアイディアだそうで、当初setfqだったものが、Lispマシンでsetfになったとのこと。

リーダーで読み出した場所に値をセットするというアイディア自体は、Deutsch氏によれば、かの有名なアラン・ケイだそうです。

  • PRINCのC

これは、調べてみても解説がみつかりませんでした。princが一番最初に登場するのは、自分が調べた限りではPDP-6 LISP(AIM-116a)なのですが、どうも、characterのcなんじゃないかという感じです。PDP-6 LISPでは、print、prin1とprincの違いは、Common Lispでもそうですが、read入力にかなった文字列を出力するかどうか、とのことですが、princは、文字通りそのまま印字するということなので…。print、prin1では、特殊文字は/によってエスケープ処理して出力されます。

  • prin1の1

そもそもprin1の1は謎なのですが、自分が調べた限りでは、PDP-1 LISPに登場するのが最初のようです。printとの違いは、前後にスペースを出力したりしない、とのこと。もしかしたら、後述の名前の後の1系の補助関数的ネーミングなのかもしれません。

  • NCONC、RPLACA

mokeheheさんのお察しのとおりで、破壊的操作系の、n〜は、no consingで、rplacaは、replace carのようです。

ちなみに、LISP 1.5には、conc(恐らくconcatenateの略)という今のappendと同じ働きをするものが存在していました。当時APPENDは、引数を2つしか取らなかったためと思われます。PDP-6 LISPで、APPENDが拡張されたためか消滅してしまいました。

おまけ

  • mapcar、maplist

大元のLISP 1.5には、MAP、MAPLIST、MAPCONとあるのですが、MAPは、'(foo bar baz)というリストがあった場合に、'(foo bar baz)→'(bar baz)→'(baz)というようにリストを処理するもので、返り値には期待しない処理に使うものだったようです。(Common Lispでの、mapl)

このMAPを規準にして考えると、各要素をCARで処理して結果をリストで返せば、MAPCAR、LISTで処理すればMAPLIST、継げれば(CONcatenate)、MAPCONということなんじゃないかと思います。また、引数の[関数 リスト]という今日のCommon Lisp、Schemeでお馴染みの順番は、PDP-6 LISPからのようです。LISP 1.5、PDP-1 LISPまでは逆でした。(INTERLISPUtilispでも逆です。)

  • 名前の後の1

foo-bar1のような名前も良くみかけますが、これは、PDP-1 LISP位からの伝統のようで、foo-barの補助関数という意味で付けられることが多いようです。似たところでは、foo-bar-1、foo-bar-aux、foo-bar*等。引数を一つとるという意味で、Gaucheのlet1などもありますね。

以上、殆ど憶測の域を出なかったりするのですが、AIメモ等を読んだりして調べてみたところでした。

last.fmと連携するなにかを作りたい: 執着篇

| 00:21 | last.fmと連携するなにかを作りたい: 執着篇 - わだばLisperになる を含むブックマーク はてなブックマーク - last.fmと連携するなにかを作りたい: 執着篇 - わだばLisperになる

cl-audioscrobblerをみつけたので、もう自作する必要はないのですが、ちゃんと機能するクライアントをみつけたということもあり、自作のものは一体どの辺がおかしくて認証を通らなかったのかを確かめてみることにしました。

結論からいうと、ケアレスミスで、 md5sum-sequenceが返す#(1 2 3 255 255)のようなベクタを"010203ffff"のような文字列に変換する際に、0でパディングするのを忘れて、"123ffff"としていた、というものでした。なるほど、そりゃ駄目ですわな…。

修正したら自作のものもポストできるようになりました。とりあえず、すっきりした…。

(require :url-rewrite)
(require :drakma)
(require :md5)

(defpackage :last.fm
  (:use #:cl #:drakma #:url-rewrite))

(in-package :last.fm)

(defclass user ()
  ((name :initarg :name :accessor name :initform "")
   (password :initarg :password :accessor password :initform "")))

(defun make-get-scrobbler-uri-string (clientid clientver user)
  (let ((base "http://post.audioscrobbler.com/?hs=true&p=1.1"))
    (concatenate 'string base 
                 "&c=" clientid
                 "&v=" clientver
                 "&u=" user)))

(defun handshake-one (clientid clientver user)
  (http-request 
   (make-get-scrobbler-uri-string clientid clientver user)))
   
(defun decode-handshake-one (clientid clientver user)
  (let ((response 
         (http-request (make-get-scrobbler-uri-string clientid clientver user))))
    (destructuring-bind (uptodatep md5-challenge post-url interval) (ppcre:split "\\n" response)
      (list (string-equal "uptodate" uptodatep)
            md5-challenge
            post-url
            (ppcre:register-groups-bind (wait) ("INTERVAL ([0-9]+)" interval)
              (values (parse-integer wait :junk-allowed 'T)))
            user))))

(defun string-to-md5-string (str)
  (string-downcase 
   (apply #'concatenate 'string
          (map 'list (lambda (x) (format nil "~2,'0,X" x))
               (md5:md5sum-sequence str)))))

(defun make-md5-response (password md5-challange)
  (string-to-md5-string
   (concatenate 'string (string-to-md5-string password) md5-challange)))

(defun current-time-string ()
  (multiple-value-bind (s m h d mo y) (decode-universal-time (get-universal-time) 0)
    (format nil "~D-~2,'0D-~2,'0D ~2,'0D:~2,'0D:~2,'0D" y mo d h m s)))

(defmethod make-submit-uri ((user user) (artist string) (track string) (album string) (length integer))
  (destructuring-bind (uptodatep md5 post-url interval username)
      (decode-handshake-one "tst" "1.0" (name user))
    (declare (ignore uptodatep))
    (values 
     (concatenate 'string 
                  post-url
                  "?u=" username
                  "&s=" (make-md5-response (password user) md5)
                  "&" (url-encode "a[0]") "=" (url-encode artist)
                  "&" (url-encode "b[0]") "=" (url-encode album)
                  "&" (url-encode "t[0]") "=" (url-encode track)
                  "&" (url-encode "m[0]") "=" ;mbid
                  "&" (url-encode "l[0]") "=" (prin1-to-string length)
                  "&" (url-encode "i[0]") "=" (url-encode (current-time-string)))
     interval)))

(defun scrobble-current-song (user &key artist track album length)
  (multiple-value-bind (uri wait)
      (make-submit-uri user
                       artist
                       track
                       album
                       length)
    (sleep wait)
    (http-request uri)))

;; テスト
(setq me (make-instance 'user :name "user" :password "password"))

(print (scrobble-current-song 
        me
	:artist "Bonnie Pink"
	:track "Private Laughter"
	:album "Even So"
	:length 179))

cl-audioscrobbler

| 00:09 | cl-audioscrobbler - わだばLisperになる を含むブックマーク はてなブックマーク - cl-audioscrobbler - わだばLisperになる

今回は、cl-audioscrobblerを試してみます。

これは、last.fmに曲情報をポストしたりする、Audioscrobblerと連携するパッケージです。

この前私はlast.fmに曲情報をポストするコードを書いて認証が通らず頓挫したりしましたが、そんな私も一応作る前にClikiを調べて、last.fm関連のものが無いかは確認してみたつもりでした。これを発見したときには、なんだ、そのものズバリがあるんじゃないかよ!、と思ってしまいました。

Clikiの検索では、存外、目的のパッケージが見付けられないことが多い気がします。

この前も、AllegroのHTMLパーザを移植してClikiに公開した人が、「それ、もう移植されてるよ」と指摘される、なんてことがありました。

ちなみに、私もClikiでpxmlutilsを発見できずに、Allegroのオープンソース版をSBCLに移植した後で発見するという、全く同じ体験をしました。

移植ははちょっと直せば良いので簡単ではあるのですが、同じことをしてる人が他にも沢山いるような気がしてなりません(笑)

pxmlutilsっていう名前が良くないと思うなあ。

パッケージ名cl-audioscrobbler
本拠地サイトcl-audioscrobbler
ClikiCLiki: cl-audioscrobbler
ASDF-INSTALL

インストール

(asdf-install:install :cl-audioscrobbler)一発です。

試してみる


(require :cl-audioscrobbler)

;; クライアント作成
(defparameter *c*
  (cl-audioscrobbler::make-client "user" "password" "tst" "1.0"))

;; 曲情報をポスト
(cl-audioscrobbler::post-song *c* :1.1
			      "Bonnie Pink"
			      "Even So"
			      "Private Laughter"
			      "179"
			      "1")

という感じで曲情報がポストできます。

プロジェクトのサイトには非常に丁寧に作られたドキュメントが沢山用意されていて非常に親切です。

2008-01-15

Lisp初心者がお勧めするLisp入門にお勧めなL-99

| 18:16 | Lisp初心者がお勧めするLisp入門にお勧めなL-99 - わだばLisperになる を含むブックマーク はてなブックマーク - Lisp初心者がお勧めするLisp入門にお勧めなL-99 - わだばLisperになる

ひげぽんさんのところのScheme情報リンク集で取り上げて頂いてからRSSのフィードの登録も急増し、凄い勢いだなあと思っているわけなのですが、何を隠そう私は、完全なるプログラミング初心者かつLisp初心者であり、しかも完全独学で誰に教わったという訳でもないので、何か悪いコードを書いても、コンパイラにしか怒られたことのない、そんな甘えん坊さんな完全趣味プログラマなのです。いや、お恥ずかしい…というか申し訳ない…。

UNIXのワークステーションを好んで使っていたために、シェルスクリプトは日常生活で使う分位は書けますが、Perlも、RubyもSchemeも、JavaもCも入門書の第一章位辺で挫折しました。

そんな私ですが、Lispの括弧と前置記法には全く抵抗がなかったためか、Lispや、Schemeは何度か繰り返してトライしては挫折を繰り返していました。

最初は、Lispに実用性(シェルスクリプトの代替となるもの)を求めていて、日頃身近に使うことにすれば自然にば身に付く筈と安易に考えていたのですが、シェルスクリプトから乗り換えようというには、GaucheやScshは、至極当然ながらあまりにもScheme的で、その表現方法を知らねばならず、またCommon Lispは、それに加えて、UNIX上でスクリプト言語として使うのは、OSを含めた文化の違いで無理ではないにしろ、手軽では無いのでこれまた挫折しました。勿論Lispマシンの上では、Lispが一番簡便なスクリプト言語ですが(笑)

そんなこんなしている去年の今頃、del.icio.usのlispタグのところに「L-99: Ninety-Nine Lisp Problems」が集中的に登録されていたので、ちょっと興味を持って挑戦してみることにしました。

これは、Lispの問題が大体難易度順に並べられた問題集です。L-99といいつつ84問しか無いのですが、元はProlog用に作られたもので、「401 Unauthorized」がオリジナルのサイトです。

この問題集には、リスト操作の問題が沢山でてくるのですが、初心者にも比較的手頃で、割と、次へ次へという感じで進むことができて、そんなこんなしているうちに、それなりにリスト操作も身に付く気がします。

それで、リスト操作が身近になってくると、マクロを書くことも身近になって来ました。結局のところLispの伝統的なマクロは、プログラムというリストを操作して目的の構造を作るということだからかもしれません。

そして、リスト操作/マクロが身近になってくるとLispの制御構造も身近になって来て、なんとなくLispで何かを表現できるようになってくる気がします。(関数型言語としてのLispが身近になるというよりは、リスト操作言語としてのLispが身近になるという感じなのかもしれませんが…。)

それと、L-99には、リスト操作だけでなく、それなりにコンピュータサイエンス的な問題もちりばめられていたりするのも良い感じです。

しかし、L-99には、若干の問題があって、

  1. 解答が全部完成していない
  2. 後半は、P-99からそのまま転載していたりするのでProlog用語そのまま

だったりします。

しかし、元のP-99を参照すればどうにかなるので、まあ大丈夫かなと。

もし自分のように、何年もLisp周辺をずっとぐるぐる回ってはいるんだけど、実際にはLispでプログラムを書くのが身近に感じられず、結局手着かず…という人がいたならば、L-99は良いとっかかりになると思うので個人的にお勧めしたいです。

それで、開発環境ですが、やはり、初心者をサポートすると言う意味でもSLIMEは強力だと思います。

コンパイルして問題があるソースの個所を色付きで教えてくれたり、関数名をガンガン補完してくれたり(例えばppcre:bindをppcre:register-groups-bindまで展開してくれる)してくれるので非常にお勧めです。

…そういう私ですが、66問目ではまってしまい、それ以上進めずにいます(;´Д`)

おまけ

L-99をPerl6で解くという試みもあるようです。

2008-01-14

cl-glfw

| 22:33 | cl-glfw - わだばLisperになる を含むブックマーク はてなブックマーク - cl-glfw - わだばLisperになる

今回は、cl-glfwをインストール。

glfwのCommon Lispバインディングの様子。

パッケージ名cl-glfw
本拠地サイト不明
ClikiCLiki: cl-glfw
ASDF-INSTALL

インストール

(asdf-install:install :cl-glfw)一発です。

glfw(libglfw.so)が必要なので、予めインストールして置く必要があります。

試してみる

デモが附属してくるので試してみます。

(load "cl-glfwのソースの場所/cl-glfw_0.3/examples/gears.lisp")

するとGLではお馴染みの歯車がぐるぐる回ります。

2008-01-13

ttf-ascii

| 17:45 | ttf-ascii - わだばLisperになる を含むブックマーク はてなブックマーク - ttf-ascii - わだばLisperになる

TTFファイルから、アスキーアートのバナーを作るらしいパッケージ。とりあえず、試しでインストール。

パッケージ名ttf-ascii
本拠地サイトhttp://www.yagc.ndo.co.uk/code/ttf-ascii/
ClikiCLiki: ttf-ascii
ASDF-INSTALL×
インストール方法ASDFには対応している。

インストール

このパッケージは、ASDFには対応しているのですが、bzip2で圧縮されているため、asdf-installできず。最近のASDF-INSTALLだとbz2も扱えるのでしょうか…。

面倒なので、asdf-installをbz2も扱えるように変更してみました。CL-PPCREとか、KMRCL依存というのに問題ありな気が激しくしますが、面倒だったので…。(find-packageで判定してオリジナルと動作を切り分ければ、幾分ましかもしれません)

;; 判定する関数をでっちあげ
(defun get-archive-type (name)
  (let ((file-type (kmrcl:command-output "~A ~A" "file" (namestring name))))
    (cond ((ppcre:scan "bzip2" file-type) :bz2)
	  ((ppcre:scan "gzip" file-type) :gz)
	  ('T :unknown))))

(defun get-tar-directory (packagename)
  (let* ((tar (with-output-to-string (o)
                (or
                 (sb-ext:run-program *tar-program*
                                     (list (case (get-archive-type packagename)
					     (:gz "-tzf")
					     (:bz2 "-tjf")
					     (otherwise (error "unknown archive type")))
					   (namestring packagename))
                                     :output o
                                     :search t
                                     :wait t)
                 (error "can't list archive"))))
         (first-line (subseq tar 0 (position #\newline tar))))
    (if (find #\/ first-line)
        (subseq first-line 0 (position #\/ first-line))
        first-line)))

(defun untar-package (source packagename)
  (with-output-to-string (o)
    (or
     (sb-ext:run-program *tar-program*
			 (list "-C" (namestring source)
			       (case (get-archive-type packagename)
				 (:gz "-xzvf")
				 (:bz2 "-xjvf")
				 (otherwise (error "unknown archive type")))
			       (namestring packagename))
			 :output o
			 :search t
			 :wait t)
     (error "can't untar"))))

それで、このパッケージは、ZPB-TTFパッケージに依存しているので、(asdf-install:install :zpb-ttf)でインストールします。

試してみる。

ドキュメントはないですが、ソース本体であるfont.lispの最後にテストが書いてあるので、それを真似て試してみます。

ちなみに、シンボルはエクスポートされていない様子です。

日本語フォントは無理かなーと思って試してみたのですが、ちゃんと表示されました。

(let ((font-loader
       (zpb-ttf:open-font-loader "/usr/share/fonts/truetype/kochi/kochi-gothic-subst.ttf")))
  (ttf-ascii::print-message (ttf-ascii::make-message font-loader "漢" 64 64))
  (zpb-ttf:close-font-loader font-loader))
;=>
################################################################
##########+#####################################################
#########++##################++############++###################
########++++#################+++++#########+++++################
#######+++++#################++++++########++++++###############
#######++++++################+++++#########++++++###############
########+++++################+++++#########+++++################
########++++++##+++++++++++++++++++++++++++++++++++++++++++++###
#########+++++##+++++++++++++++++++++++++++++++++++++++++++++###
#########++++++#+++++++++++++++++++++++++++++++++++++++++++++###
##########+++++++++++++++++++++++++++++++++++++++++++++++++++###
###########++++#+++++++++++++++++++++++++++++++++++++++++++++###
###########++################+++++++++++++++++++################
#############################+++++++++++++++++++################
#############################+++++++++++++++++++################
########################+++++++++++++++++++++++++++++###########
########################+++++++++++++++++++++++++++++###########
######+#################+++++++++++++++++++++++++++++###########
####+++#################+++++++++++++++++++++++++++++###########
###+++++################++++#########+++#########++++###########
###++++++###############++++#########+++#########++++###########
####+++++###############++++#########+++#########++++###########
####++++++##############++++#########+++#########++++###########
#####+++++##############++++#########+++#########++++###########
#####++++++#############++++#########+++#########++++###########
######+++++#############++++#########+++#########++++###########
######++++++############++++#########+++#########++++###########
#######++++#############+++++++++++++++++++++++++++++###########
#######++###############+++++++++++++++++++++++++++++###########
########################+++++++++++++++++++++++++++++###########
########################+++++++++++++++++++++++++++++###########
########################+++++#######+++++#######+++++###########
####################################+++++#######################
####################################+++++#######################
##############++#####+++++++++++++++++++++++++++++++++++########
##############+++####+++++++++++++++++++++++++++++++++++########
#############++++####+++++++++++++++++++++++++++++++++++########
#############+++++###+++++++++++++++++++++++++++++++++++########
############++++++###+++++++++++++++++++++++++++++++++++########
############++++++################++++++########################
###########+++++++################+++++#########################
###########++++++#################+++++#########################
##########+++++++++++++++++++++++++++++++++++++++++++++++++++###
##########+++++++++++++++++++++++++++++++++++++++++++++++++++###
#########++++++++++++++++++++++++++++++++++++++++++++++++++++###
#########++++++#+++++++++++++++++++++++++++++++++++++++++++++###
#########++++++#+++++++++++++++++++++++++++++++++++++++++++++###
########++++++#################+++++++#+++++####################
########++++++#################++++++##++++++###################
#######+++++++################+++++++###+++++###################
#######++++++################++++++#####+++++++#################
######+++++++################++++++######+++++++################
######++++++#################++++++######++++++++###############
#####+++++++###############+++++++#########+++++++##############
#####++++++###############++++++++#########+++++++++############
####++++++###########++#+++++++++###########++++++++++##########
####++++++#########+++++++++++###############+++++++++++########
###+++++++#####++++++++++++++##################+++++++++++#+####
#########+#####+++++++++++++####################++++++++++++####
################++++++++++########################+++++++++#####
################+++++++++############################+++++######
#################++++###########################################
################+###############################################
################################################################

cl-sparsematrix

| 16:07 | cl-sparsematrix - わだばLisperになる を含むブックマーク はてなブックマーク - cl-sparsematrix - わだばLisperになる

Clikiで更新されたものを片っ端からインストールしてみるということは、当然ながら自分が理解できる以外のものもインストールするということであり、今回は、疎行列に関してのパッケージなわけですが、疎行列って何?という私が試してみるのも無理がありますね(´▽`*)

ということで、インストールしただけで終了しました…。

作者は、Tamás K Papp氏で、他にも色々面白そうなパッケージを作成されている方のようです。

パッケージ名cl-sparsematrix
本拠地サイト404 Not Found
ClikiCLiki: cl-sparsematrix
ASDF-INSTALL
(asdf-install:install :cl-sparsematrix)

インストール

asdf-install可能なので、(asdf-install:install :cl-sparsematrix)すると、関連するパッケージを芋蔓式に取得します。

cl-numlib、ffa、cl-utilities等がインストールされるようです。

丁寧なPDFのドキュメントが附属してきます。

2008-01-12

lisp-unit

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

Clikiからは新しく登録されたり更新されたパッケージ(ライブラリ)のRSSがフィードされているのですが、ふと思い立って、更新されたパッケージを追い掛けてやたらインストールして、試したログをここに書いてみることにしました。

色々な面白いパッケージが沢山あるのですが、如何せん情報がみつけられないことが多いので、片っ端からインストールしてみれば、何か面白いもの発見できるかな、という安易な考えです。

とりあえず、今回は、lisp-unitを試してみることにしました。前までは、LispUnitという名前だったのですが、この度名前をlisp-unitに変更したようです。

パッケージ名lisp-unit
本拠地サイトno title
ClikiCLiki: lisp-unit
ASDF-INSTALL
(asdf-install:install :lisp-unit)

インストール

asdf-install可能なので、(asdf-install:install :lisp-unit)で一発インストール完了です。

使ってみる

かなり古くからある、リグレッションテストのパッケージ:RTと大体同じような使い勝手に思えました。

テストをDEFINE-TESTで定義して、RUN-TESTで実行するところなど共通点は多いと思います。

(defpackage #:cl-2008-01-12 
  (:use #:cl))

(in-package #:cl-2008-01-12)

(defun fib (n)
  (cond ((< n 2) n)
	('T (+ (fib (1- n))
	       (fib (- n 2))))))

(defun fib-iter (n)
  (do ((cnt n (1- cnt))
       (a1 1 (+ a1 a2))
       (a2 0 a1))
      ((< cnt 2) a1)))

のようなファイル(cl-2008-01-12.lisp)があるとすると、

(require :lisp-unit)

(load "cl-2008-01-12")

(defpackage :test_cl-2008-01-12
  (:use #:cl #:lisp-unit))


(in-package #:test_cl-2008-01-12)


(define-test fib
  (assert-eql 55 (cl-2008-01-12::fib 10))
  (assert-eql 1 (cl-2008-01-12::fib 1))

  (loop :for i from 1 :to 30
        :do (assert-eql (cl-2008-01-12::fib-iter i) (cl-2008-01-12::fib i)))
  
  )

(run-all-tests :test_cl-2008-01-12)

(cl-2008-01-12::fib-iter 0)
;=>FIB: 32 assertions passed, 0 failed.

のように書いてテストが書けます。

RTでは、deftestに一つずつ別の名前を付けなくちゃいけなかったりするので、自分は、それ用のマクロを定義して使ったりしてましたが、lisp-unitはRTより使い勝手は良いようです。

本拠地のサイトに丁寧な説明が用意されています。

自分も一時ユニットテストを使っていたのですが、テストを書くのが段々面倒になってしまい、ひたすらSLIMEのコンパイルエラーの指摘に頼ってコードを書くようになってしまいました。

これを使ってもう少し、精神衛生上すっきりしたコード書きができるようになりたいものです。

SLIMEで支援用ELISPを書いてCompile Defunと同じ位簡単にテストまで完了したいという妄想はあるのですが、なかなか自分の力量的に難しいなあ。

2008-01-11

関数名にハイフンを使うようになったのはいつ頃からなのか

| 23:32 | 関数名にハイフンを使うようになったのはいつ頃からなのか - わだばLisperになる を含むブックマーク はてなブックマーク - 関数名にハイフンを使うようになったのはいつ頃からなのか - わだばLisperになる

LISPでは、関数や変数の名前にハイフンを使いfoo-if-barのような名前を付けたりして、これが、それなりにLisp的な特徴とみなされているかと思います。

非常にどうでも良いことなのですが、急に、関数名にハイフンを使うようになったのはいつ頃からなのか、ということが気になり出して仕方なくなってしまい、色々調べてみました。本当にどうでも良いことなのですが…。

そもそもなんで疑問に思うかといえば、LISP 1.5をちょっといじってみて、マニュアルや古い文献のプログラム等を眺めてみているのですが、今だったらハイフンで区切るような、例えば、SUPER-REVERSEのような関数名が、SUPERREVERSEだったりするのです。これだと、読み難いのですが、しかし、他の長い名前も全然ハイフンが使われていないのです。

LISP 1.5では、変数や関数にハイフンを含められないのかといえば、そういう制限もなく、普通に使ってプログラムも書けます。

そうなってくると、じゃあ、一体いつからなんだということになってきてしまうのです。

それで、とりあえず、調べてみたのですが、PDP-1 LISPは、ハイフン抜き、直系のPDP-6 LISPも抜きで、手元のソースを眺める限りでは、その次のMACLISPで初めて登場するようです。

大体、1974年位が境なんですが、なんでその辺なのかは良く分かりません。

それで、その後1978以降MIT Lispマシン周辺で、やたら長い名前をハイフンでつなげた名前が爆発的に増えます。

ちなみに、INTERLISP系ですが、こちらは、BBN-LISPから、INTERLISP-Dまであんまりハイフンで継げた名前はみかけません。

以上、まったく役に立ちようもない探究でした。

ちなみに、これも非常にどうでも良いことなのですが、過去のMITのAIメモを眺めたりしている過程で、内部的な関数の頭に%ではなく、*を付けるという習慣を発見しました。PDP-6からMACLISP初期のことで、大体40年前のことなのですが、例えば、2引数しか取らない限定的なGREATERPは、*GREAT、といった風になっています。頭に%を付けるのは、多分これもLispマシン周辺から来てるんじゃないかと思うんですが、これまた、本当にどうでも良いことでございまして…。

Lispマシン情報リンク集

| 15:15 | Lispマシン情報リンク集 - わだばLisperになる を含むブックマーク はてなブックマーク - Lispマシン情報リンク集 - わだばLisperになる

2008-01-10

TIのLispマシン - TI-Explorerのエミュレータ

| 15:00 | TIのLispマシン - TI-Explorerのエミュレータ - わだばLisperになる を含むブックマーク はてなブックマーク - TIのLispマシン - TI-Explorerのエミュレータ - わだばLisperになる

Lispマシンというのは、ある意味Lisp文化の頂点の一つだったんじゃないかと思うのですが、今年は、Lispマシン近辺をもう少し探索してみたいなと思い、手始めに、これまで、別のブログに書いていたLispマシン関連の記事もこのブログにカテゴリ:「Lispマシン」として纏めてみることにしました。

これまでは、MIT CADRや、INTERLISP-Dを、ちょっといじったりしてました。結構情報は古くなってしまったんですが…。

それで、今回は、TI-Explorerのエミュレータの導入をまとめてみることにしました。

TI Explorerとは

テキサスインスツルメンツも昔はLispマシンを製造していまして、その名をExplorerといいました。

これは、Greenbratt氏のLMIからライセンスを受けて製造されていたマシンということで、LMI系のLispマシンです。

MITのCADRを起源としているので共通点は非常に多いのですが、それなりに違いはあります。

このTI Explorerのエミュレータを開発して配布しているところがあるので、ありがたく使わせて頂きます。

Merokoという名前のようです。

なんか、日本のアニメのキャラから名前がとられているっぽいのですが、元ネタが分からない…。

インストール

ここでは、Ubuntu 7.10 x86_64にインストールする方法を書きます。

もともとコンパイル済みのLinux用のバイナリが同梱されているので、32bit環境ならば何もしなくても起動できるとは思います。

  • ダウンロード
$ wget http://www.unlambda.com/download/meroko/meroko-20060610.tar.gz
$ wget http://www.unlambda.com/download/meroko/X1-DISKS.tgz
  • 解凍
tar zxvf X1-DISKS.tgz
tar zxvf meroko-20060610.tar.gz

解凍するとsvnフォルダができます。この中に、ソースと実行バイナリがあります。

自分は、x86_64用にMakefileを書き換えて、コンパイルしなおしてみています。といってもCPUの指定を変更するだけです。

--- svn/Makefile        2006-06-11 23:57:03.000000000 +0900
+++ /share/sys/emu/Explorer64.emu/svn/Makefile  2007-12-22 07:49:53.000000000 +0900
@@ -15,7 +15,7 @@
 DISPLAY_OBJ = sdl.o
 endif
 
-CFLAGS= -O3 -ggdb -Wall -march=pentium3 -mfpmath=sse -mmmx -msse $(DEFINES) -DENABLE_ENET
+CFLAGS= -O3 -ggdb -Wall -mfpmath=sse -mmmx -msse -msse2 -msse3 $(DEFINES) -DENABLE_ENET
 # -DTRACELOG
 # -DDEI_TRACELOG
 # -DTRACELOG2

コンパイルすると、merokoという実行ファイルができるのでこれを直接実行します。

実行ファイルと同じディレクトリにディスクイメージを解凍してできたX1-DISKSディレクトリを移動します。デフォルトだと1階層上にMerokoというディレクトリを作成してそこに置くみたいですが、同じディレクトリに配置することにしてみます。

いじってみる

./meroko

で起動します。

  • 起動時に何やら質問されるのですが、とりあえず、D=Default。
  • ネットワーク関連で色々文句を言われるのですが、とりあえず無視。Ctrl-Zでトップレベルに抜けられます。
  • リスナーが立ち上がっている状態なので、ここに色々式を入力します、たとえば、(ed)でZmacsが起動します。
  • ホームディレクトリの作成
(fs:create-directory ";g000001")

";"がパスネームセパレータの様です。この場合、ルートディレクトリ直下にg000001というディレクトリを作成します。

  • ログイン
(login 'ユーザ名)

で、ログインします。デフォルトでは、ルート直下の同じログイン名と同じ名前のディレクトリがホームディレクトリとして利用されます。

ログイン初期化ファイルを作成する

リスナーに(ed)と打込むか、System Eでエディタが起動します。エミュレータでは、Systemは、F1にアサインされています。(E→エディタ、L→リスナー、P→Peek(PSのようなもの)、M→メーラ)

Zmacsが起動するので、自分用のログイン初期化ファイルを作成してみましょう。

ファイル名は、login-init.lispで、

Emacsと同様にC-X C-Fで作成します。

ログインされると、この中のLispの式が評価されます。

とりあえず、簡単なところで、自分は、

(tv:black-on-white) 
(ed "g000001;gazonk.del")

のようにしています。

black-on-whiteは、画面を黒地に白にする命令で、ed〜は、ログインしたら、Zmacsで、gazonk.delというファイルを開けということです。

大体こんな感じなのですが、基本的なEmacsの機能は大体あるので、色々試してみると面白いかと思います。

Lispの開発もSlimeのようにかなり対話的に行なえます。エディタ上の式を評価するには、C-Shift-E、コンパイルするには、C-Shift-Cです。画面のエコーエリアか、長い場合は、画面の上から、スルスルと結果が表示されてきます。これが結構良い感じで、SLIMEもこういう風に表示できたら良いのにな、とか思います。

マニュアルとかシステムのソースとか

膨大なマニュアルがbitsaversに保管されています。

システムコードの一部がウェブで公開されています

まとめ

以上、非常に簡単にですが、インストールからログイン位までで、自分の知っていることを書いてみました。

Lispマシンってどんな感じだったのか雰囲気位は、感じられるかと思います。

SymbolicsのLispマシンはもっと凄くて、TI Explorerより、もっと洗練されていて驚く程、高機能です。ただ、エミュレータは公開されているものの本格的に利用するには、Symbolicsのソースが必要なようです。こちらも、ちょっと起動するところまではできたので、そのうち纏めて書いてみたいと思います。

このエミュレータは重たくて、Core2位でないと使いものにならないかもしれません。また、どうやら外部とネットワークで通信もできるようなのですが、ドキュメントも少なく自分は、まだ使いこなせてません。もし、なにか情報お持ちでしたら、ツッコミ等大歓迎です!

2008-01-09

Practical Common Lisp (22)

| 19:12 | Practical Common Lisp (22) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (22) - わだばLisperになる

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

半挫折気味です(笑)、気力が…。

Keyword Parameters

  • オプショナル引数は便利だが、キーワード引数を使うとそれだけでは解決できないこともできる。
  • &optional、&restの後だったらどこでも使える。
(foo)                ==> (NIL NIL NIL)
(foo :a 1)           ==> (1 NIL NIL)
(foo :b 1)           ==> (NIL 1 NIL)
(foo :c 1)           ==> (NIL NIL 1)
(foo :a 1 :c 3)      ==> (1 NIL 3)
(foo :a 1 :b 2 :c 3) ==> (1 2 3)
(foo :a 1 :c 3 :b 2) ==> (1 2 3)
  • 上記のように引数定義の順番ではなく、キーワードの名前と指定した値が対になる。キーワードを省略した場合は、&optionalのようにデフォルト値が使われる。また、同様にsupplied-pも使用可能。
  • 外向のインターフェースとしては、指定した名前を使用し、内部では別の名前で処理したい場合、下記のように書ける。
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
  (list a b c c-supplied-p))

;=> (foo :apple 10 :box 20 :charlie 30) ==> (10 20 30 T)

; :appleで値を受けとるが、内部では、aに結び付けられているので、aで参照。

PCLとは無関係な個人的な深追いと混乱メモ

CLtL2を読んでいたら、キーワードについてさらに詳しい(というか自由度の高い)使い方が説明されていたのでメモ

PCLでの例のように(:apple a)という風に記述すれば、内部では違う名前で扱えるわけですが、

(defun foo (&key ((:apple a) 0))
  `(apple -> ,a)

(foo :apple 10)
;=> (apple -> 10)

キーワードで使うシンボルは、キーワードパッケージのシンボルでなくても良いみたいで、

(defun foo (&key ((apple a) 0))
  `(apple -> ,a )

とも書けます。

それで、呼び出しはどうなるのかと言えば、

(foo 'apple 1)
;=> (apple -> 1)

(foo :apple 1)
;=> エラー

となり、定義に使用したキーワードが属するパッケージのシンボルである必要があるようです。

上記のようにパッケージ名を省略すれば、カレントパッケージになるわけですが、見た目で混乱するので、たとえば、cl-userのシンボルを使うとすると、

(defun foo (&key ((cl-user::apple a) 0))
  `(apple -> ,a )

(foo 'cl-user::apple 1)
;=> (apple -> 1)

となります。

(defun foo (&key foo)
  (print foo))

をややこしく書くと、

(defun foo (&key ((keyword::foo foo)))
  (print foo))

ということみたいです。

しかし、キーワードにキーワードパッケージ以外を指定するような状況ってどういうのがあるんでしょう…。

お題: 西暦 to 和暦

| 02:01 | お題: 西暦 to 和暦 - わだばLisperになる を含むブックマーク はてなブックマーク - お題: 西暦 to 和暦 - わだばLisperになる

今回の問題は、西暦を和暦に直す問題。といっても明治以降で対応すれば良し、とのこと。

しかし、明治6年までは太陰暦だったらしく、その辺をどう対応するのかはお題ではふれられていないので、意見が分かれている感じ。

…自分は対応しないということで、作成。

しかし、いつも下手なコードを投稿しているけれど、我ながら、今回はいつもに増してひどい気がするなあ(;´Д`)。

こういう予め決められたパラメータが多く出現するような問題の場合、パラメータの扱いをどうしたら良いのかが、いまいち分からない…。

2008-01-07

ABCL 0.0.10をインストールしてみる

| 17:31 | ABCL 0.0.10をインストールしてみる - わだばLisperになる を含むブックマーク はてなブックマーク - ABCL 0.0.10をインストールしてみる - わだばLisperになる

※antを使う方法が一番楽なようです

----

私は、割とOSや処理系のインスコ厨です。

特に使う目的もない処理系をインストールして、それで満足して終了、ということを繰り返しているのですが、今回は、ABCLをインストールしてみることにしました。

ABCLはJavaVM上で動くCommon Lispの処理系です。

に詳しい解説があります。

私のインストールポリシーとしては、

  1. /usr/local/abcl/0.0.10以下にファイルを置く

ということ位です。

必要なもの

  1. とりあえず、JDKが必要です。ubuntu 7.10 x86_64には、sun-java5-jdkがあるので、パッケージで導入しました。
  2. Common Lisp処理系。コンパイルするのに必要になります。今回はSBCLを使用しました。

インストール

  1. READMEを読んで、customizations.lispを設定します。
(case *platform*
  (:windows
   (setq *jdk*           "C:\\Program Files\\Java\\jdk1.5.0_11\\")
   #+(or) (setq *java-compiler* "jikes")
   )
  (:darwin
   (setq *jdk*           "/usr/")
   (setq *java-compiler* "jikes")
   #+(or) (setq *jar*    "jar"))
  ((:linux :unknown)
   (setq *jdk*           "/usr/lib/jvm/java-1.5.0-sun/")
   (setq *jar*           "jar")))
  1. ホストの処理系でbuild-abcl.lispをロードします。
(load "build-abcl.lisp")
  1. ホストの処理系でコンパイル開始
(build-abcl:build-abcl :clean t :full t)

しばし待つとabclという起動の為のスクリプトが生成されるので、これで完了です。

なんとなく分かったこと

  • ASDFは組み込みで、(require :asdf)で使えます。
  • SLIMEに対応している。
  • CL-PPCRE、ITERATE等、それなりに動くパッケージはある。

分からないこと

  • UTF-8でSLIMEと通信できない模様。ただ、処理系はUTF-8なので、日本語が表示できないというわけではない様子。
  • イメージのダンプとかできるんだろうか。とりあえずは、初期化ファイルを作って、swankまで起動させて、SLIMEと通信して使用してみています。起動は、
/usr/local/abcl/0.0.10/abcl --load init-abcl.lisp

Armed Bear Common Lisp 0.0.10 (built Mon Jan 7 2008 16:27:15 --900)
Java 1.5.0_13 Sun Microsystems Inc.
Java HotSpot(TM) 64-Bit Server VM
Control-C handler installed.
Low-level initialization completed in 0.398 seconds.
Startup completed in 1.822 seconds.
〜〜
;; Swank started at port: 4013.
Type ":help" for a list of available commands.
CL-USER(1)

といった感じ。

2008-01-06

last.fmと連携するなにかを作りたい: 頓挫篇

| 23:43 | last.fmと連携するなにかを作りたい: 頓挫篇 - わだばLisperになる を含むブックマーク はてなブックマーク - last.fmと連携するなにかを作りたい: 頓挫篇 - わだばLisperになる

レトロなLispも良いけど何か普段使うツールのようなものを何か作りたいなあと思い、アカウントを作ったものの、あまり利用していないlast.fm用の簡単なクライアントをCommon Lispで書けないものかと、ちょっと調べてみました。

サーバとの通信の手順は、解説ページにまとまっていました。

ということで、何の考えもなく、そのページを頭から読みつつ、ガチャガチャコードを書きながら動作を確認してゆくことに。

曲情報の投稿までの流れとしては、

  1. クライアント、ユーザの情報をサーバに投げる。
  2. サーバから認証用のキーと投稿用URLと待機時間が送られてくる
  3. 送られて来た情報とパスワードをMD5でエンコードし、曲の情報と一緒にして投げる

という風な感じです。

途中までは順調だったのですが、サーバとの認証のところでどうもMD5でエンコードしたものの結果が違うらしく引っ掛かってしまい、BAD AUTHとなり先に進めず。

説明では、

The MD5 response is md5(md5(your_password) + challenge),
where MD5 is the ascii-encoded, lowercase MD5 representation,
and + represents concatenation.
MD5 strings must be converted to their hex value before concatenation
with the challenge string and before submission to the final MD5 response.

ということなのですが、md5(md5(your_password) + challenge)なので、一回目のハンドシェイクで送られて来たMD5のキーとMD5でエンコードしたパスワードをくっつけ、さらにそれをMD5でエンコードする、という風に読めるのですが、どうも上手く行かず。MD5のエンコードが違ったりするのかなと思い、md5sumで確認してみても生成されるキーは同一で、それ自体には問題はない様子。うーん、それとも、認証じゃなくて全然違うところで引っ掛かってしまっているのか。

こうなると他の言語での実装を調べてみるしかなさそうな気配。

しかし、他の言語は殆ど読めないのだよなあ(;´Д`)


(defpackage :last.fm
  (:use #:cl #:drakma #:url-rewrite #:md5))

(in-package :last.fm)

(defun make-get-scrobbler-uri-string (clientid clientver user)
  (let ((base "http://post.audioscrobbler.com/?hs=true&p=1.1"))
    (concatenate 'string base 
		 "&c=" clientid
		 "&v=" clientver
		 "&u=" user)))

(defun handshake-one (clientid clientver user)
  (http-request 
   (make-get-scrobbler-uri-string clientid clientver user)))
   
(defun decode-handshake-one (clientid clientver user)
  (let ((response 
	 (http-request (make-get-scrobbler-uri-string clientid clientver user))))
    (destructuring-bind (uptodatep md5-challenge post-url interval) (ppcre:split "\\n" response)
      (list (string-equal "uptodate" uptodatep)
	    md5-challenge
	    post-url
	    (ppcre:register-groups-bind (wait) ("INTERVAL ([0-9]+)" interval)
	      (values (parse-integer wait :junk-allowed 'T)))
	    user))))

(defun string-to-md5-string (str)
  (apply #'concatenate 'string
	 (map 'list (lambda (x) (string-downcase (write-to-string x :base 16)))
	      (md5sum-sequence str))))

(defun make-md5-response (password md5-challange)
  (string-to-md5-string
   (concatenate 'string (string-to-md5-string password) md5-challange)))

; - UTC date/time in YYYY-MM-DD hh:mm:ss format
(defun current-time-string ()
  (multiple-value-bind (s m h d mo y) (get-decoded-time)
    (format nil "~D-~2,'0D-~2,'0D ~2,'0D:~2,'0D:~2,'0D" y mo d h m s)))

(defun make-submit-uri (data artist track album length)
  (destructuring-bind (uptodatep md5 post-url interval user) data
    (declare (ignore uptodatep))
    (values 
     (concatenate 'string 
		  post-url
		  "?u=" user
		  "&s=" (make-md5-response "password" md5)
		  "&a0=" (url-encode artist)
		  "&t0=" (url-encode track)
		  "&b0=" (url-encode album)
		  "&m0="		;mbid
		  "&l0=" length
		  "&i0=" (url-encode (current-time-string)))
     interval)))

(defun scrobble-current-song (artist track album length)
  (multiple-value-bind (uri wait)
      (make-submit-uri (decode-handshake-one "tst" "1.0" "g000001")
		       artist
		       track
		       album
		       length)
    (sleep wait)
    (http-request uri)))

;; TEST
(print (scrobble-current-song "Bonnie Pink"
			      "Private Laughter"
			      "Even So"
			      "179"))


BBN-LISPのADVICE

| 22:11 | BBN-LISPのADVICE - わだばLisperになる を含むブックマーク はてなブックマーク - BBN-LISPのADVICE - わだばLisperになる

comp.lang.lispを眺めていたら、SymbolicsのLispマシンのマニュアルがbitsaversにアップされたとのことで、早速ダウンロードして眺めたりしておりました。

それで、bitsaversの他のディレクトリを探索していたら、BBN-LISPのマニュアルがありました。

BBN-LISPはINTERLISPの前身ともいえる存在です。

それで、Symbolicsそっちのけで眺めてみていたら、19章(P423)にADVICEという文字が。

Emacsを使っている方だったらdefadviceは知っているかと思うのですが、それと同じパターンの機構で、ある関数にフックをかけてカスタマイズするという仕組のようです。

CLOSならビフォアとアフターメソッド的ともいえるかもしれません。BBN-LISP、INTERLISPには、Daniel G. Bobrowが深く関与していますが、CLOSにも関与しているようなので、そういう影響もあるのかもしれません。BBN-LISP→INTERLISP→COMMON LOOPS→CLOSという流れ。

しかしそれよりも、このマニュアルが出たのは、1971、2年ということで、こういう仕組みが35年前に考えられていて実装されていたということに驚かされました。

他にも先進的な試みが沢山あるようで、INTERLISPの特徴は殆どBBN-LISPの時点で既に実装されているようです。

こんなことに感動しているのは自分くらいかもしれませんが、とりあえず記念にメモ(´▽`*)

Practical Common Lisp (21)

| 21:21 | Practical Common Lisp (21) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (21) - わだばLisperになる

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Rest Parameters

  • 引数の扱いには、様々あるが引数の数を可変にしたいことがある。例えば、FORMATは、2つ以上の引数を取るが、2つ目以降は必要に応じて与える引数が増減する。+も同様。0個の場合もあり、(+)は0となる。
(format t "hello, world")
(format t "hello, ~a" name)
(format t "x: ~d y: ~d" x y)
(+)
(+ 1)
(+ 1 2)
(+ 1 2 3)
  • 必須引数や、オプショナル引数等をやりくりして対応することもできるが、対応させるのは困難。。ちなみに取れる最低限50以上は引数が取れることを保証することが規格で定められている。実装により上限は異なるが、CALL-ARGUMENTS-LIMITで処理系の上限を確認できる。(SBCL 1.0.13では、1,152,921,504,606,846,975だった)
  • このような問題を解決するのが、&RESTパラメータで、&RESTに指定された変数がリストとなり、以降の引数が格納される。FORMATや、+は下記のように定義できる。
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...) 

2008-01-05

FORMATは深すぎる

| 18:09 | FORMATは深すぎる - わだばLisperになる を含むブックマーク はてなブックマーク - FORMATは深すぎる - わだばLisperになる

一体どんな機能があるのか極一部しか把握できていないFORMATなのですが、今日ふとFORMATTER関数ってどういう時に使うと効果的なのかな、と思い、CLtL2のFORMATTERのところを読んでみてました。

それで、FORMATは、フォーマット指定文字列だけでなく、関数も引数として取れるということを初めて知ったのですが、FORMATTERというのは、フォーマット指示文字列に従って引数を解釈して文字列を出力する関数とのこと。

うーん、そうだったのか!、と思い色々実験。

(format t (lambda (stream &rest args)
	    (do ((cnt 0 (1+ cnt))
		 (a args (cdr a)))
		((endp a))
	      (format stream "Arg:~D => ~A~%" cnt (car a)))
	    args) 
	'foo 'bar 'baz)
;=>
;Arg:0 => FOO
;Arg:1 => BAR
;Arg:2 => BAZ

定義する関数は、2引数の関数で、streamと残りの引数、といった構成。つまりformatが取る引数と同じ構成にする模様。

うーん、これだ!という便利そうな良い例が思い付かないけれど、なんだか凄そうだー。

(defun type-of-printer (stream &rest args)
  (dolist (item args)
    (format stream "Arg:~A Type => ~A~%" item (type-of item)))
  args)

(format t #'type-of-printer 'foo '(foo bar baz) 1 2 3 4)
;=>
;Arg:FOO Type => SYMBOL
;Arg:(FOO BAR BAZ) Type => CONS
;Arg:1 Type => BIT
;Arg:2 Type => (INTEGER 0 1152921504606846975)
;Arg:3 Type => (INTEGER 0 1152921504606846975)
;Arg:4 Type => (INTEGER 0 1152921504606846975)

(funcall (formatter "Hello, ~{~A ~^~}!~%") t '(foo bar baz))
;=>
;Hello, FOO BAR BAZ !

(let ((print-items-in-list (formatter "~{~A~%~}") ) )
  (funcall #'print-items-in-list t '(foo bar baz)))
;=>
;FOO
;BAR
;BAZ

(mapc (formatter "~A~%") '(t t t) '(foo bar baz))
;=>
;FOO
;BAR
;BAZ

(format t #'format #'format #'format "Hello, ~@(~A~)" 'world!)
;=>
;Hello, World!

こうやってみてみると、FORMATっていうのは、外枠だけ提供してるようなもので、どっちかというとFORMATTERが頑張っている構成になっているように見える。

2008-01-04

お題: ポーカーの役判定

| 01:21 | お題: ポーカーの役判定 - わだばLisperになる を含むブックマーク はてなブックマーク - お題: ポーカーの役判定 - わだばLisperになる

とりあえず新しいお題が出題されたので挑戦。

毎度ながらノープランで書き始め、なんとか完成。

コピペミスで古いバージョンをアップしてしまい修正。

Slimeだと開発が対話的で便利過ぎるのかLispのコアイメージの中の環境とファイルのソースに書かれた内容が食い違うことも発生しがちなので、提出の際には一旦コードを素の状態からロードさせて正しく動作するか確認するようにしないといけないな、と改めて思ったり。

しかし、多分、私は、Slimeの支援なしでは、何一つ書けないんだろうなと思う。

ちょっと書いて、直してコンパイル、コンパイラに怒られて、直してコンパイル、〜という感じで、コンパイラが指摘してくれないと、どこにも辿り着けない(´▽`*)

PAIP読書会

| 00:50 | PAIP読書会 - わだばLisperになる を含むブックマーク はてなブックマーク - PAIP読書会 - わだばLisperになる

PAIP読書会が開催されることになったとのことで、早速PAIPを持ってない私もメーリングリストにだけは、登録してみたのですが、告知メールが今日届きました。

--

「Paradigms of Artificial Intelligence Programming:

Case Studies in Common Lisp」

by Peter Norvig

の読書会を開催します。とりあえず第1回を下記の日程と場所で実施し、

そこでいろいろ相談しながら、ゆる~くやり方を決めていきたいと考え

ています。

日時: 2008年1月12日(土) 13:00~18:00

場所: 株式会社タイムインターメディア2F大会議室

東京都新宿区坂町26-27 インテリジェントプラザビル 2F

参加費: 無料

参加条件: 特にありませんが、上記の本とCommon Lisp処理系をインストールした

ノートパソコンを持参することをお薦めします。

その他: 持ち込みネタ(PAIP本に関わる発表とかデモとか大歓迎です。)

質問や提案があれば、WiLiKiのページかこのメーリングリスト、あるいはLingrのGauche部屋に流してもらえるとありがたいです。

また、この告知自体の転載を歓迎します。

--

とのことで、参加しようかどうか悩んでいます。PAIP持ってないしな〜。

この本の中のコードは、公開されてるから、それとPCを持参すれば少しはましかな、うーん。

まあ、速攻PAIP買えよって話なんですけど、この本自分にはちょっと難易度が高すぎるんじゃないかなと思いまして。やっぱり、とりあえず様子見かな(笑)

Practical Common Lisp (20)

| 00:16 | Practical Common Lisp (20) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (20) - わだばLisperになる

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Optional Parameters

オプショナルパラメータの話

  • 全部の関数が予め決められた数の引数を扱うなら良いが実際的には、例えば、パラメータに初期値が設定されていて、呼び出し時にもし必要な場合、パラメータを指定したりできると便利。
  • そういうときには、&OPTIONALが使える。
(defun foo (a b &optional c d) (list a b c d))
  • 上記の場合、&optionalより前のaとbは必須パラメータとなり、それより後は、オプショナルになる。
  • 呼び出し時にオプショナル引数に値を渡さないとNILで初期化してくれる。
(foo 1 2)     ==> (1 2 NIL NIL)
(foo 1 2 3)   ==> (1 2 3 NIL)
(foo 1 2 3 4) ==> (1 2 3 4)
  • NILで初期化するのは分かったが、他の値で初期化して欲しい場合、明示的に指定する方法があり、(変数名 初期値)という風にリストにして記述する
(defun foo (a &optional (b 10)) (list a b))

(foo 1 2) ==> (1 2)
(foo 1)   ==> (1 10)
  • 引数の値を他の引数から算出して設定したい場合もある。例えば、矩形を描く関数を定義するとして、基本的にデフォルトは正方形を描くものとし、高さは、明示的に与えられない限り、幅から算出されるとしたい。その場合、先に決定した値を後方で参照して初期値として使用することができる。
(defun make-rectangle (width &optional (height width)) ...)
  • 偶に、引数が明示的に与えられたのか、それとも初期値が使われたのかを判別したい場合がある。その場合には、(変数名 初期値 指定されたかどうかを判別する変数名)という形式で記述する。
(defun foo (a b &optional (c 3 c-supplied-p))
  (list a b c c-supplied-p))

なんとなく演習

(defun オプショナルな気分 (&optional (ans nil))
  (print
   (if ans
       "オプショナルな気分なんですね?"
       "オプショナルな気分ではないのすね?")))

(オプショナルな気分 nil)
"オプショナルな気分ではないのですね?" 

(オプショナルな気分)
"オプショナルな気分ではないのですね?" 
;; いや、そういうわけではないのだが…。

;; 上の定義では、区別が付かないので改善
(defun オプショナルな気分 (&optional (ans nil ans-supplied-p))
  (print 
   (cond ((and ans-supplied-p (not ans)) "オプショナルな気分ではないのですね?")
	 (ans "オプショナルな気分なんですね?")
	 ('T "なんともいえないのですね?"))))

(オプショナルな気分 nil)
"オプショナルな気分ではないのですね?" 

(オプショナルな気分)
"なんともいえないのですね?" 
;; いや、そういうわけでもないのだが…。

--

質問に答えること自体は任意で、否定したい場合は、NILで答えるとした場合、オプショナル引数を省略したためにデフォルト値のNILが渡って来たのか、それとも明示的にNILで答えられたのか区別できないので、supplied-pを利用するという無理矢理なケースを捏造してみる。

2008-01-03

お題: Hello, World! 等々

| 15:03 | お題: Hello, World! 等々 - わだばLisperになる を含むブックマーク はてなブックマーク - お題: Hello, World! 等々 - わだばLisperになる

最初は、Lisp1.5で問題を解くってのはかなり難しいのではと思っていましたが、そこそこ行けるんじゃないかと思えて来ました。

現在6/120なのでコンプリ率は約5%。Lisp 1.5のコーナーできたら良いなー。

(2)お題: Hello, world!(Lisp1.5)

文字の出力で空白の出力方法が分からなかったので後回しにしていたHello, World!

マニュアルに基本的な出力方法が記載されていたので、それを試してみた。

それにしても謎が多いLisp 1.5。

(40)お題: 与えられた数字のケタ数(Lisp1.5)

他の方は、文字列に変換して文字列の長さから桁を割出していましたが、

Lisp1.5では若干無理があるので、素直に勘定することに。

Lisp 1.5をさわってみて若干分かったことなど、なんの役にも立たないメモ

  • cond
    1. (cond (述部 実行部?))の実行部が暗黙のprognでない。また省略もできない。そのため、空の場合、明示的にNIL、F、()を置く。実行部には複数の式を書いてもエラーにはならないが、一番上だけ実行されて、あとは無視される。progで囲んだり、prog2で囲む必要あり。(しかし、マニュアルには載ってるけどprog2がみつからない…)lambdaで囲んでも丸ごと無視されるので、progで安定の模様。
    2. デフォルト節の述部は、(quote T)か、Tか*TRUE*で受ける。
  • M式

大文字で書かれたシンボルはquoteされていることを表わしているらしい。つまりM式のTは、S式で(quote t)のことで、律義に(quote t)と書いている人も結構いる。MACLISPで、'Tと書いたりする人がいるのは、この伝統を受け継いでいるのかもしれない。ちなみに、自分も'Tと書いていたら、点がないのはなんか寂しい気がして毎度付けてしまう変な癖が…。

2008-01-02

Practical Common Lisp (19)

| 23:30 | Practical Common Lisp (19) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (19) - わだばLisperになる

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Function Parameter Lists

  • パラメータリストの主な役割とはもちろん引数をどう受けとってどのように関数本体の処理に渡すかということ
  • パラメータリストが変数名のみで構成された単純なリストの場合、それぞれの要素は必須パラメータと呼ばれる。
  • この場合、関数が呼ばれる際は、指定したパラメータそれぞれに対応する引数が必ず割り当てられなければならない。対応する引数は、各パラメータに束縛される。関数の呼び出し時に渡される引数が多くても少なくてもエラーとなる。
  • しかしながら、Common Lispのパラメータリストにはより柔軟な引数処理の指定もできる。
  • まず、前述の必須パラメータに加えて、省略可能なオプショナルパラメータがある、また、引数をリストで纏めてリストで受けとることもできる。また、引数の位置の出現位置の順番で対応を指定するのではなく、指定したキーワードに結び付けて取得する方法、というようにCommon Lispには色んな場合に対応できる方法を提供している。

お題: コラッツ・角谷の問題 等々

| 22:46 | お題: コラッツ・角谷の問題 等々 - わだばLisperになる を含むブックマーク はてなブックマーク - お題: コラッツ・角谷の問題 等々 - わだばLisperになる

(120)お題: コラッツ・角谷の問題(Common Lisp)

お題の名前からして、久々に数学的で複雑な問題だー、と思って無理だと思い放置して風呂に入って寝ようと思っていましたが、どうにも気になって眠れなかったので、どういう仕組になるのかだけ簡単にさらってみることにしてみたら、そんなに嫌がる程の問題でもなかった様子。 別名3n+1問題ともいわれる有名な問題らしいです。 ひたすらCPUをぶん回すだけのコードでしたが、とりあえずできたので投稿してみました。Common Lisp部門2着。 他の方々は、賢くメモ化とかして高速化をはかっていました。そうなのか! メモ化とかするもんなのか! 投稿してから自分も真似して自作のものをメモ化などをして高速化をはかってみましたが、ハッシュでのメモ化による高速化具合と、数字を全部fixnumで宣言し2の割り算を左シフトしてコンパイラの頑張り任せにする馬鹿バージョンを計測したところ、どういうわけか割り算をシフトにした方が速かったので、まあ、これでも良いや、ということにしました。 fixnum宣言+左シフトは、SBCLそのままの割り算に比べて8倍位速くなるんですよね。どういう理屈かは知りませんが…。 3の掛け算も右シフト+Nにした方が若干速くなるし、fixnum宣言とシフト命令って相性が良いんでしょうか。まあ、fixnumの範囲を越えるとまずいですが、今回は範囲内なのでOKです。

(27)お題: リストを逆順に表示 (Lisp 1.5)

Common Lispに加えて、馬鹿っぽくLisp 1.5でも挑戦してみることにしました。 標準でREVERSEはあるっぽいんですが、自分が使ってるキットにはないので自作。まあ、次回からREVERSEの定義は省略ということで。

(16)お題: アレイのuniq (Lisp 1.5)

MEMBERも標準で付いてくるっぽいけどないので自作。マニュアルで確認しないといけないのが若干面倒なのでLisp 1.5関数一覧とか作ろうと思ったり。

(49)お題: 隣り合う二項の差 (Lisp 1.5)

一応再帰でも書けるので、再帰で書いてみた。

(120)お題: コラッツ・角谷の問題(Lisp 1.5)

Common Lispの内容をLisp 1.5で。7時間回して一応正しい答えだったので投稿。
  THE TIME ( 0/ 0  000.0) HAS COME, THE WALRUS SAID, TO TALK OF MANY THI
NGS .....   -LEWIS CARROLL-
 END OF EVALQUOTE OPERATOR
             FIN      END OF LISP RUN
それはさておき、Lisp 1.5では、実行が終わるとこのように表示されるんだけれども、この時間のところに実行時間とか入って欲しいなと。 どうするんだろう…。エミュレータじゃ無理なのだろうか。

2008-01-01

Practical Common Lisp (18)

| 13:37 | Practical Common Lisp (18) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (18) - わだばLisperになる

やっとこさPractical Common Lisp 第5章5. Functionsです。この章も文字だけみたいなので、読んでメモするという感じで…。

5. Functions

  • 文法と意味論の後は、Lispの3つの基本要素である、関数、変数、マクロをみてゆく。
  • 3章で既にこれら3つは使用していたが、うわべだけでどのように機能しているかの詳細には立ち入らなかった。
  • 今後の2、3章で、これらの話題を扱う。他の言語で対応する諸機能と対象させて考察してみたり等々…。
  • Lispの大半は、関数で成り立っていて、言語規格で定められたものの3/4は関数が占める。
  • すべての組込みデータ型は、関数がどのようにデータを扱うかということにより定められている(?)
  • CLOSでさえ、関数/総称関数の上に成り立っている。これらは、16章で扱う。
  • Lisp流儀にはマクロというものが非常に重要なものになるが、すべての真の機能性は関数によってもたらされているものである。

Defining New Functions

  • 通常関数定義にはDEFUNが用いられ下記のように書かれる
(defun name (parameter*)
  "Optional documentation string."
  body-form*)
  • どんなシンボルでも関数名に使える。通常、アルファベットとハイフンのみが多い。それ以外は特定の命名規則に則って使用されることが多い。
  • 例えば、ある形式のものを他の形式に変換するような関数の場合、"-"で連結されて一つの名前になることがある。文字列をウィジェットに変換する場合は、string-widgetといった風。
  • 重要な事項としては、2章でも述べたが、語句の連結に、"-"を使いアンダースコアや、キャメルケースで書くことはしないということで、frob-widgetは、frob_widgetや、frobWidgetより好ましい。
  • 関数の引数リスト部は変数がどのように関数に渡されるかを定義する。引数を取らない場合は、空リストの()で表記される。
  • 引数の取り方の指定は、色々ある。必須とされるもの、オプション扱いのもの、複数の指定、キーワードの指定。
  • 引数のパラメータリストの次が文字列の場合、その文字列は、その関数を説明に使われるドキュメント文字列となる。関数が定義された場合、ドキュメント文字列は、DOCUMENTATIONで参照できるように定義される。
  • 最後に関数の主となる定義部分である本体になる。式は順に評価され末尾で評価された式が関数の値となるか、RETURN-FROMを用いて指定した値で関数を脱出することもできる。
  • 2章で記のような関数定義をしたが、これを詳細にみると下記のようになる。
    1. 関数の名前:hello-world
    2. 引数:()リストなので、引数はとらないことがわかる
    3. ドキュメント文字列はなし
    4. 一つの式のボディ
(defun hello-world () (format t "hello, world"))
  • もう少し複雑なものをみると、下記のようになる。
    1. 関数の名前は、verbose-sum
    2. 2つの引数を取り、xとyに束縛される。
    3. ドキュメント文字列を持つ。
    4. ボディは、2つの式から成り、返り値は、+が引数を処理した値が、verbose-sum全体の返り値となる。
(defun verbose-sum (x y)
  "Sum any two numbers after printing a message."
  (format t "Summing ~d and ~d.~%" x y)
  (+ x y))