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-02-29

LISP引きこもり生活 (4) はてな日記をCLから更新する その2

| 11:02 | LISP引きこもり生活 (4) はてな日記をCLから更新する その2 - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (4) はてな日記をCLから更新する その2 - わだばLisperになる

昨日のコードでは、どうもポストする度にどんどん追記されていってしまうようなので、更新する際には一旦削除してから再度ポストすることにしてみました。

もっとちゃんとした正しい方法があると思うんですが…。

ログアウトしてない所為かと思ってログアウトもするようにしたんですが、ログアウトするかしないかは追記の件とは関係ない様子。

上書きモードとか、追記モードがあるのかしら。

;; 使い方

;; Lispマシン的気分のための設定
(login-setq *hw-base-url* "http://cadr.g.hatena.ne.jp/g000001")

;; ポスト
(let ((cj (hw:login))
      (base *hw-base-url*))
  (hw:delete-entry base cj)
  (hw:post base
	   (hw::file-to-string "g000001/group/cadr/2008-02-29.txt")
	   cj)
  (hw:logout cj))

;; ----------------
(defun delete-entry (base-url cj &optional date)
  (let ((date (or date (today-string))))
    ;; confirm
    (http-request (sconc base-url "/edit") 
		    :external-format-in :utf-8 
		    :external-format-out :utf-8
		    :method :post
		    :cookie-jar cj
		    :parameters 
		    `(("mode" . "delete")
		      ("rkm" . ,(rkm cj base-url))
		      ("date" . ,date)))

    ;; delete
    (http-request (sconc base-url "/deletediary") 
		    :external-format-in :utf-8 
		    :external-format-out :utf-8
		    :method :post
		    :cookie-jar cj
		    :parameters 
		    `(("mode" . "enter")
		      ("rkm" . ,(rkm cj base-url))
		      ("date" . ,date)))))

(defun logout (cj)
  (http-request "https://www.hatena.ne.jp/logout" 
		:external-format-in :utf-8 
		:external-format-out :utf-8
		:cookie-jar cj))

ArcでL-99 (P24 ロトくじ)

| 10:37 | ArcでL-99 (P24 ロトくじ) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P24 ロトくじ) - わだばLisperになる

今回は、ロトくじの様に数列からランダムに任意の個数の数字を抜き出したリストを返すというお題です。

ヒントとして以前に作成したrnd-selectとrangeを使用する、とあります。

前回のお題でも若干疑問に感じていたのですが、並びもランダムにしようと思うと、remove-atのように要素を元のリストから要素を落す方法では、並びは元のリストを継承してしまいます。

その辺をどうするのかと。

問題をみると並びまでばらばらなので、rnd-selectを変更して、残った要素と、落とす要素の二つのリストをリストにして返すことにしました。Arcには多値がないようなのでリストで。

(lotto-select 6 49)
;=> (20 44 31 36 1 9)

(def rnd-select (lst num)
  (and (< 0 num)
       ((afn (lst acc cnt)
	  (if (or no.lst (is len.lst num))
	      (list lst acc)
	      (let pos (+ 1 (rand len.lst))
		(self (remove-at lst pos)
		      (cons (lst (- pos 1)) acc)
		      (+ 1 cnt)))))
	lst () num)))

(def lotto-select (n rng)
  (cadr:rnd-select (range 1 rng) (- rng n)))

2008-02-28


LISP引きこもり生活 (3) はてな日記をCLから更新する

| 18:30 | LISP引きこもり生活 (3) はてな日記をCLから更新する - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (3) はてな日記をCLから更新する - わだばLisperになる

ログインシェルをGaucheに変更したら、普通のシェルを呼び出すことが前提の今の自分のEmacsの設定では、Emacs上からのgrepとか色々支障を来たすようになりました。

はてなの日記はSimple Hatenaモードからはてダラを呼び出して更新しているのですが、これもどうやら上手く動かなくなった様子。

ということで、原因を追及するのも良いのですが、CLから日記を更新することにしてみました。

はてな日記用のAPIは公開されていないようなので、適当にHTMLから情報を切り出し。

今のところ投稿することしかできませんが、まあ良しとします。

適当過ぎる作りなので野生味溢れる使用感ですが、いつの日かSimple Hatena Modeから呼び出して更新できるようにしたいところです。

(defpackage :hw
  (:use :cl :drakma :kmrcl))
(in-package :hd)

(declaim (inline sconc))
(defun sconc (&rest args)
  (apply #'concatenate 'string args))

(defun read-password-file (&optional (path (merge-pathnames ".hatena" (user-homedir-pathname))))
  (aif (probe-file path)
       (with-open-file (str it :direction :input)
	 (let ((user-alist (read str nil nil)))
	   (values (cdr (assoc :username user-alist))
		   (cdr (assoc :password user-alist)))))
       (error "初期化ファイル:~~/.hatenaが存在していません。:~A" path)))

(defun login ()
  (let ((cj (make-instance 'cookie-jar)))
    (multiple-value-bind (user password)
	(read-password-file)
      (values cj 
	      (http-request "https://www.hatena.ne.jp/login" 
			    :external-format-in :utf-8 
			    :external-format-out :utf-8
			    :method :post
			    :cookie-jar cj
			    :parameters `(("name" . ,user) 
					  ("password" . ,password)))))))

(defun rkm (cj base-url)        ;rkmってなんだか分からないけど、rkmを取得する
  (aand (nth-value 1
	  (ppcre:scan-to-strings 
	   "rkm.*'(.*)'"
	   (http-request (sconc base-url "/edit") 
			 :external-format-in :utf-8 
			 :external-format-out :utf-8
			 :method :post
			 :cookie-jar cj)))
	(aref it 0)))

(defun today-string ()
  (multiple-value-bind (ig no re d mo y)
      (decode-universal-time (get-universal-time))
    (declare (ignore ig no re))
    (format nil "~D~2,'0D~2,'0D" y mo d)))

(defun post (base-url text cj &optional date)
  (let* ((date (or date (today-string))))
    (ppcre:register-groups-bind (y m d) ("(....)(..)(..)" date)
      (http-request (sconc base-url "/edit") 
		    :external-format-in :utf-8 
		    :external-format-out :utf-8
		    :method :post
		    :cookie-jar cj
		    :parameters 
		    `(("mode" . "enter")
		      ("rkm" . ,(rkm cj base-url))
		      ("date" . ,d)
		      ("trivial" . "0")
		      ("year" . ,y)
		      ("month" . ,m)
		      ("day" . ,d)
		      ("title" . "")
		      ("body" . ,text))))))

(defun file-to-string (file)
  (format nil "~{~A~^~%~}" 
	  (series:collect 
	    (series:scan-file file #'read-line))))

;; 使い方
(setq base-url "http://cadr.g.hatena.ne.jp/g000001")
(setq *cj* (login))			;ログインして、cookie jarにクッキー保存

; ポスト
(post base-url (file-to-string "/home/g000001/hatena/g000001/group/cadr/2008-02-28.txt")
      *cj*)

ArcでL-99 (P23 ランダムに指定した個数の要素を選択)

| 12:10 | ArcでL-99 (P23 ランダムに指定した個数の要素を選択) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P23 ランダムに指定した個数の要素を選択) - わだばLisperになる

今回は、ランダムに指定した個数の要素を選択するというお題です。

ヒントとして以前に作成したremove-atとシステムに用意された乱数生成関数を利用して良いとのこと。

Arcには、乱数生成用のrand、引数の中からランダムに一つ返すrand-choice、シーケンスからランダムに一つ返す、random-elt等、妙に充実しています。

(rnd-select '(a b c d e f g h) 3)
;=> (a c d)

(def rnd-select (lst num)
  (and (< 0 num)
       ((afn (lst cnt)
	  (if (or no.lst (is len.lst num))
	      lst
	      (self (remove-at lst (+ 1 (rand len.lst)))
		    (+ 1 cnt))))
	lst num)))

2008-02-27


ArcでL-99 (P22 指定した範囲の数列のリストを作成する)

| 16:28 | ArcでL-99 (P22 指定した範囲の数列のリストを作成する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P22 指定した範囲の数列のリストを作成する) - わだばLisperになる

今回は、指定した範囲の数列のリストを作成するのがお題です。

SRFI-1でいうところのiotaですね。

Arcにはこれと同じ機能のrangeがあります。

Arcもバージョン2となり、そして、ちょっと前ですが、Emacsのarc-mode.elを作った方が現われました。待ってました!

やっぱり専用のモードがあるというのは良いです。

;=> (rangE 4 9)
(4 5 6 7 8 9)

;=> (range 4 9)
(4 5 6 7 8 9)

(def rangE (start end)
  (and (<= start end)
       ((afn (cnt acc)
	  (if (> cnt end)
	      rev.acc
	      (self (+ 1 cnt) (cons cnt acc))))
	start () )))

Symbolicsと闘っていた頃のRMSのLispコード

| 03:50 | Symbolicsと闘っていた頃のRMSのLispコード - わだばLisperになる を含むブックマーク はてなブックマーク - Symbolicsと闘っていた頃のRMSのLispコード - わだばLisperになる

RMSがGNUプロジェクトを始める前に、Symbolicsと闘っていたというのは伝説となっていますが、それは1980年代前半頃の話のようです。

Joe Marshall氏が公開しているLMI Lambdaのソースコードには、その時期のものがあるのですが、探してみるとRMSの署名があるものも何点か存在します。

等々…。

ソースの冒頭に信条的なものが書いてあるのですが、GNUプロジェクトを予感させるものだったりして興味深いです。

Lispマシンを知らない子ども達

| 02:58 | Lispマシンを知らない子ども達 - わだばLisperになる を含むブックマーク はてなブックマーク - Lispマシンを知らない子ども達 - わだばLisperになる

あまりも当たり前過ぎて21世紀に入ってから言葉にだしたことはあまりないのですが、当然のことながら、3D CGといえばSymbolicsのLispマシン、Silicon GraphicsのUNIXワークステーションなんかよりSymbolicsざます!という時代がありました。

とはいえ、IRIXもSymbolicsも今は亡きものになってしまったのですが…。

一体いつの時代の話なのかと言えば、1980年代後半から90年代前半位の話のようですね。それと、システムの金額も半端ではなく、一般のご家庭にも全然縁がないお値段でした。そりゃ知らないわ。

この動画もRainer Joswig氏のサイト経由で知ったのですが、1987年のCGの解説ビデオで、SymbolicsのLispマシンと、Lispマシンで作ったCGがばんばん登場しています。20年前ということをしっかりと念頭に置いて観賞すると、これは凄い!と感動できるかもしれません。

ちなみに、Silicon Graphicsのワークステーションが欲しいという奇特な方がいらっしゃいましたらどうか貰って下さい。Octane、Indigo2(紫/緑)、O2等々各種取り揃えております。Fuelもあるんですが、これは若干迷っています…。

あ、SunのUltra80も良かったら差し上げます。メモリ4GBで、4CPUです。450Mhzですけどね…。

梱包ができる気がしないので、手渡しだと最高なんですが…。

LISPでハードウェア周りの記述はできるのか?

| 02:15 | LISPでハードウェア周りの記述はできるのか? - わだばLisperになる を含むブックマーク はてなブックマーク - LISPでハードウェア周りの記述はできるのか? - わだばLisperになる

「LISPでハードウェア周りの記述はできるのか?」という疑問はわりと頻繁に繰り返されていると思いますが、ハードウェアの上に直にLISPが載っているLispマシンがあった位なので可能なんでしょう。

詳しくは自分も知らないのですが、The Evolution of Lispによれば、Lispマシンが開発された当初、MacLISPから枝分かれしたLispマシン用のLisp Machine Lispでは、システム記述力が足りなかったため一部アセンブリで記述する必要があったとのことで、この辺をを全面的にLISPで書けるように強化/改良したのが、Zetalispとのことです。

Lisp Machine LispとZetalispの違いは曖昧で、Symbolicsの3600シリーズと共に登場したのが、Zetalispのようなのですが、MITでもLMIでもZetalispと呼ばれていて、何がなんだか良く分かりません。マニュアルも共通ですし。

こちらのRainer Joswig氏のサイトには同氏がキャプチャした沢山のLispマシンの動画が沢山置いてあるのですが、このNXP1000の解説では、Lispで書かれたSCSIドライバのコードを開いて見せたりしています。ちなみにこの動画は非常に画面サイズが大きいです…。また、このサイトは自宅サーバで、Wgetも禁止だそうなので、ご注意願います。といっても動画のサイズも大きいのですが…。

CLtL->CLtL2->ANSI CLという流れではなかった

| 01:29 | CLtL->CLtL2->ANSI CLという流れではなかった - わだばLisperになる を含むブックマーク はてなブックマーク - CLtL->CLtL2->ANSI CLという流れではなかった - わだばLisperになる

自分は、ブログに書くLISPネタを思い付いたり見付けたりしたら、fixdapにメモしているのですが、ブログに書かないうちにどんどん埋没して行ってしまいます。

しかし、溜めておいてもしょうがないので、小ネタでもどんどん書いてみることにしました。

ということで、今回は、comp.lang.lispを眺めていて、おっ、と思ったことを。

Seriesのスレッドだったので読んでいたのですが、ANSI Common Lispに至る話で、Kent M. Pitman氏からCLtL2からANSI CLという流れではないんだよ、との指摘。

割とFAQなのかもしれませんが、自分は知りませんでした。そうだったのか!

ということで、CLtL2を確認してみたのですが、やっぱりそういう風な記述が序文等にありました。

CLtL -> ANSI CL

CLtL -> CLtL2

ってことみたいです。

2008-02-26

ArcでL-99 (P21 指定した位置に要素を挿入する)

| 23:49 | ArcでL-99 (P21 指定した位置に要素を挿入する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P21 指定した位置に要素を挿入する) - わだばLisperになる

今回は、指定した位置に要素を挿入する関数の作成がお題です。

前回のものをちょっと細工して終了。

(insert-at 'alfa '(a b c d) 2)
;=> (a alfa b c d)

(def insert-at (item lst pos)
  (unless (<= 1 pos (len lst))
    (err "The index is bad for a sequence of length."))
  ((afn (lst acc cnt)
     (if (or no.lst (is pos cnt))
	 (join rev.acc (list item) lst)
	 (self cdr.lst
	       (cons car.lst acc)
	       (+ 1 cnt))))
   lst () 1))

2008-02-25

LISP引きこもり生活 (2) LispMに少しでも近付きたい年頃

| 15:28 | LISP引きこもり生活 (2) LispMに少しでも近付きたい年頃 - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (2) LispMに少しでも近付きたい年頃 - わだばLisperになる

とりあえず、前回OSの操作はできるだけSLIMEから操作して、Lispのイメージから外出することは控える、と心に決めました。

今回は、やはり気分が一番大事なので、Lispマシンを操作している感を演出するため、Lispマシンのログイン周りを真似てみようかと思います。

Lispマシンはもともとはシングルユーザ前提で開発がスタートしたこともあるのかユーザ管理については比較的素朴なようです。

とりあえず最初期のMIT CADRのマニュアルを眺めながら、Lispマシンを真似して適当に作ります。

まずは、ログインですが、loginを使い、ユーザ名の設定と、初期化ファイル(ユーザディレクトリのlispm.init)の読み込みをします。

対になるのは、logoutで、これは、ユーザが設定した変数等をログアウト時に元に戻すもののようです。

元に戻す方法は、非常に素朴で、ユーザのログイン時のみ有効な変数を設定する専用の関数を使用し、その関数は、値の設定と同時に、設定をアンドゥする式をlogout-listというリストに格納します。

そして、ログアウト時にそのリストをevalすることによって元に戻すということになっています。

ログイン変数用にlogin-setq、他の汎用的なログイン時の設定変更にはlogin-evalがあり、主に初期化ファイル内で使用します。

ちなみにTAOには、セミグローバル変数という、ユーザがログインしている間だけ有効という変ったものがあったようです。

ということで、マニュアルの説明から適当に想像して作ってみました!

Lispマシンのソースを見て作ったわけではないので、定義については適当です…。そのうちちゃんと読んでみようかと…。

ただ単にログインしただけでは、寂し過ぎるので、とりあえず、Stumpwm(Common Lispのウィンドウマネージャ)とemacsclientの組み合わせを活かして未読メールがある場合は、Wanderlustを起動するようにしてみました。(Emacs側ではWanderlustをenv-mailという関数でラッピングしています)

;; 動作

(setq foo 777)
foo
;=> 777

(login 'g000001)
;=>
;11件の未読メールがあります。メーラーを起動しますか? (y or n)
;        〜〜Wanderlust起動〜〜
;Hi.

(login-setq foo 33 bar 22)

foo
;=> 33

logout-list
;=> ((MAKUNBOUND 'BAR)(SETQ FOO 777)(MAKUNBOUND 'WATCH-MAILDIR) )

;(logout)

foo
;=> 777

bar
;=> error

;; 真似
(defparameter user-id ""
  "The value of user-id is either the name of the logged in user, 
as a string, or else an empty string if there is no user logged in.
 It appears in the who-line.")

;logout-list Variable
(defparameter logout-list ()
  "The value of logout-list is a list of forms 
which are evaluated when a user logs out.")

(defun login (name &optional (load-init))
  "If anyone is logged into the machine, login logs him out.
 (See logout .) Then user-id is set from name.
 Finally login attempts to find your INIT file. 
It first looks in \"user-id ; .LISPM (INIT)\", then in \"(INIT); 
user-id .LISPM\", and finally in the default init file
 \"(INIT); * .LISPM\". When it finds one of these that exists,
 it loads it in. login returns t ."
  (setq user-id (string name))
  (unless load-init
    (load (merge-pathnames "lispm.init" (user-homedir-pathname)))))

(defun logout (&optional name)
  "First, logout evaluates the forms on logout-list.
 Then it tries to find a file to run, looking first in 
\"user-id ; .LSPM_ (INIT)\", then in \"(INIT); user-id .LSPM_\", 
and finally in the default file \"(INIT); * .LSPM_\". 
If and when it finds one it these that exists, 
it loads it in. Then it sets user-id to an empty string and
 logout-list to nil , and returns t ."
  (declare (ignore name))
  (setq user-id "")
  (eval `(progn ,@logout-list))
  (setq logout-list () ))

(defmacro setq-return-undo (var val)
  "setqを実行し、実行内容をアンドゥする式を返す。2値目は、setqの返り値"
  `(let ((undo (if (boundp ',var)
		   '(setq ,var ',(and (boundp var) (symbol-value var)))
		   '(makunbound ',var))))
     (push undo logout-list)
     (values undo (setq ,var ,val))))

(defmacro login-setq (&rest form)
  "login-setq is like setq except that it puts a setq form on
 logout-list to set the variables to their previous values."
  `(progn
     ,@(do ((l form (cddr l))
	    (res () (cons `(nth-value 1 (setq-return-undo ,(car l) ,(cadr l)))
			  res)))
	   ((endp l) (nreverse res)))))

(defmacro login-eval (&rest form)
  "login-eval is used for functions which are \"meant to be called\" 
from INIT files, such as eine:ed-redefine-keys, 
which conveniently return a form to undo what they did.
 login-eval adds the result of the form x to the logout-list."
  `(progn
     ,@(loop :for l :in form
	     :collect `(push ,l logout-list))))

lispm.initの例

;; -*- lisp -*-

(in-package :home)

(login-setq watch-maildir '("/home/g000001/Maildir/inbox/new/*.*"
			    "/home/g000001/Maildir/WebApp/new/*.*"))
(defun mail ()
  (kmrcl:command-output "~A ~A" "emacsclient -e" "'(wl)'"))

(defun 未読さん ()
  (let ((mails (reduce (lambda (res x) (+ (length (directory x)) res))
		       watch-maildir
		       :initial-value 0)))
    (cond ((zerop mails)
	   (format t "未読メールはありません。~%"))
	  ('T (when (y-or-n-p "~D件の未読メールがあります。メーラーを起動しますか?" mails)
		(mail))))))

;;
(未読さん)
(format t "Hi.~%")

ArcでL-99 (P20 指定した要素を削除)

| 03:36 | ArcでL-99 (P20 指定した要素を削除) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P20 指定した要素を削除) - わだばLisperになる

今回は、指定した要素を削除するというお題です。

割とこのL-99(というよりP-99)というのは、以前に解いた問題を応用して新しい問題を解かせるということにおいても秀逸で、なるほど!と感心することが結構あります。

(remove-at '(a b c d) 2)
;=> (a c d)

(def remove-at (lst pos)
  ((afn (lst acc cnt)
    (if (or no.lst (> cnt pos))
	(join rev.acc lst)
	(self cdr.lst
	      (if (is cnt pos)
		  acc
		  (cons car.lst acc))
	      (+ 1 cnt))))
   lst () 1))

2008-02-24

LISP引きこもり生活 (1)

| 05:09 | LISP引きこもり生活 (1) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (1) - わだばLisperになる

私は実生活では、生きることに疲れ、貯金を食い潰しつつ、ひきこもり生活をしているわけなのですが、一歩進んで、コンピュータ環境的にはLISPに引きこもることにしてみました。なんとなく、面白そうなので。

具体的にどういうことかというと、理想としての最終引きこもり形態としては、Lispマシン上から一歩も外に出ないような生活となります。

しかし、そんなことは現実問題として無理なので、この理想にどうにか近付きつつ、現状の快適さもできるだけ損なわない、というのを目標にしようかと思います。

色々段階はあるかと思いますが、始めてみないことには分からないので、とりあえずLISP系のアプリケーションで代替できそうなものは徐々に置き換えて行くことにします。

まず、最初に何から置き換えようかと考えたのですが、やはり、ログインシェルかなと思います。

Lispマシン上で日常作業はどのように行われていたのかは、知る由もないのですが、なんとなくの私の見当では、UNIXのように、インタラクティブにシェルを操作するのではなくて、エディタのバッファ上から、色々な操作を行っていたのではないかなと想像しています。

実際Lispマシンのエミュレータを使ってみても、エディタ上のLispの式を評価することによってOSを操作できるので、エディタ上で大概のことは操作できてしまいます。

なんというか、Emacsの*scratch*バッファで、OSを全部操作するような感じで。

恐らく、シェルのインタラクティブ性というものは、端末からログインして作業する、ということを前提に発展してきたのだと思いますが、Lispマシンはその辺の考え方も違っていそうです。この辺は現状でも生き残っているSmallTalkはどうなっているのか興味があるところですね。

それで、エディタからなんでも操作するのを実現する方法なのですが、SLIMEを使っていて、非常にこれに近いものを感じるので、何でもSLIMEごしに作業することにしてみました。

そしてそれと同時に、便利なZshの使用を停止し、SLIMEから操作した方が楽な状況に強制的に追い込むことにしようということで、ログインシェルをSCSHとGaucheに変更。Zshよ、8年間ありがとう…。

そのうち、initをCLの処理系に置き換えられればと思いますが、さすがに無理かな(笑)

とりあえず、ログインシェルは置き換えたので、SLIMEから操作するということは、どういうことになるのか、適当に何か作って試してみることに。

第1弾として、wgetもどきを作ってみました。

この位なら、まだ手の届くところかなとは思いますが、システム管理とかどうするんだろう…(´▽`*)…。

;; 使用例
(wget "http://prdownloads.sourceforge.net/scsh/scsh-0.6.7.tar.gz" "/var/tmp/")
;=>
; http://prdownloads.sourceforge.net/scsh/scsh-0.6.7.tar.gz ==> /var/tmp/scsh-0.6.7.tar.gz
; ....................................................................................................102400
; ....................................................................................................204800
; ...

;; 定義
(defpackage :home 
  (:use :cl)
  (:export :wget))

(in-package :home)

(defun wget (uri &optional (dir "./"))
  (let* ((file-name (aref (nth-value 1 (ppcre:scan-to-strings ".*/([^/]*)$" uri)) 0))
	 (out-file (concatenate 'string dir file-name)))
    (format t "~A ==> ~A~%" uri out-file)
    (with-open-file (out out-file
			 :direction :output 
			 :if-exists :supersede
			 :element-type 'unsigned-byte)
      (with-open-stream (str (drakma:http-request uri :want-stream 'T))
	(do ((s (read-byte str nil -1) (read-byte str nil -1))
	     (cnt 0 (1+ cnt)))
	    ((= -1 s) (format t "end ~A.~%" cnt))
	  (write-byte s out)
	  (when (and (zerop (rem cnt 1024)) (not (zerop cnt)))
	    (princ ".")
	    (when (zerop (rem cnt (* 100 1024)))
	      (format t "~A~%" cnt))))))))

たまにするならこんな拡張

| 01:03 | たまにするならこんな拡張 - わだばLisperになる を含むブックマーク はてなブックマーク - たまにするならこんな拡張 - わだばLisperになる

Arcのafn(On Lispでのalambda)は、使い捨て感覚の関数に付ける名前を考えなくても良いので、個人的に気に入って使っているのですが、引数の取り方がlambdaと一緒のため本体部分が長くなると読み辛くなるかなあと思います。

そこで、形式をlambdaからletにしたものはどうかなと思い、そんなのを作ってみることにしました。要するに名前付きletのアナフォリック版ということですね。

それで名前をどうするか考えたんですが、Arcでletは1引数なので、そうなるとwithになるかと思い、awithにしてみました。

全体的には割とすっきり書けて良い感じにも思えるのですが、引数部分がなんとなくごちゃごちゃしてる気もします。

また、ドットで連結するのに馴れると(+ 1 foo)のようなものも1+.cntのように書きたくなってくるので、 古式ゆかしいadd1という名前の復活も試してみることにしました。

(mac awith (binds . body)
  (let b (pair binds)
    `((afn ,(map car b)
	,@body)
      ,@(map cadr b))))

(set add1 [+ _ 1])
(set sub1 [- _ 1])

;; 使用例
(def rotate (lst pos)
  (let pos (mod pos len.lst)
    (awith (lst lst acc () cnt 0)
      (if (or no.lst (is pos cnt))
          (join lst rev.acc)
	  (self cdr.lst
		(cons car.lst acc)
		add1.cnt)))))

2008-02-23

ArcでL-99 (P19 指定した位置でローテーションさせる)

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

今回は、リストの指定した位置でローテーションさせるというお題です。

ヒントとしては、P17を参照せよとのこと。

また、インデックスはマイナスの数値も扱えるようにするみたいです。

(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)

(def rotate (lst pos)
  (let pos (mod pos len.lst)
     ((afn (lst acc cnt)
	(if (or no.lst (is pos cnt))
	    (join lst rev.acc)
	    (self cdr.lst
		  (cons car.lst acc)
		  (+ 1 cnt))))
      lst () 0)))

再帰が難しいのか、それとも考え方が難しいのか

| 03:39 | 再帰が難しいのか、それとも考え方が難しいのか - わだばLisperになる を含むブックマーク はてなブックマーク - 再帰が難しいのか、それとも考え方が難しいのか - わだばLisperになる

再帰は難しいと良く言われます。自分も再帰を使ったアプローチでは、何だかこんがらがることが多いです。

しかし、良く良く考えてみると、再帰が難しいというよりも、問題を解く方法がややこしいだけで、それをもって再帰がややこしいというのは何だか変な気がしてきました。

再帰するということと、問題に対するアプローチは別の物なのに、それが一緒くたにされて、「再帰は難しい」とされているような。

例えば、リストの長さを調べるlenという関数を作ってみるとします。

まず、この問題に対してアプローチは色々取れると思うのですが、

1. 要素を勘定していって、最後に勘定した数を返す

2. リスト中の要素を全部1に置き換えて、最後に内容を全部足し算する

3. リスト中の要素を全部1を足す関数に入れ子状に置き換えて、その一番内側の関数に0という引数を与える。

等々あるかと思います。

自分が単純に考えると、やはり1番のアプローチが一番最初に思い付くのですが、再帰の入門解説等では、一番単純なものとして

(defun len (lst)
  (if (null lst)
      0
      (+ 1 (len (cdr lst)))))

のようなものが取り上げられることが多いと思います。これは、

(defun len (lst)
  (if (null lst)
      0
      ((lambda (x) (+ 1 x)) (len (cdr lst)))))

ということで、考え方としては、3番目のものに該当するかと思いますが、再帰云々の前に、考え方が既にややこしいんじゃないかと思うんですよね。

それで、1番目のアプローチですが、これは再帰でも書けて

(defun len (lst acc)
  (if (null lst)
      acc
      (len (cdr lst)
	   (+ 1 acc))))

のようになるかと思います。

リストを一個ずつ手繰って行く部分と、合計を溜めて行くところがあって、リストが空になったら終了。最後に溜めていた合計を返すというもの。

2番目のアプローチも、1番目に似ていますが、合計を溜めて置く代わりに要素を1に置き換えたリストを溜めていって、最後に+という関数をリストに適用します。

(defun len (lst acc)
  (if (null lst)
      (apply #'+ acc)
      (len (cdr lst)
	   (cons 1 acc))))

ということで、再帰を使ったからといって難しくなる訳ではないのではないかと。

1番目と2番目のアプローチは普通のループに書き直すことも容易で、末尾再帰とも呼ばれ、ループ自体が再帰の特殊形であるということもなんとなく分かります。

逆に考え方としては分かりやすいと思われているループを使ったアプローチならば何でも簡単かというと、そうでもなく、例えば、lenを拡張して、中身にリストがある場合ば、中身の個数も全部数えるような、super-lengthというものを作るとします。

これもアプローチは色々あると思うのですが、

1. 要素がリストでないならば、1を足し、リストならば、今迄計算した内容をどこかに退避させて、そのリストを調べ、その合計を退避した内容と合計し…を繰り返す。

2. リスト中の要素を全部1を足す関数に置き換え最後に0という引数を与える。要素がリストの場合、そのリストに自分自身を適用する。

のようなものがあると思います。1はループで表現可能で、2は自分自身を再度呼び出すので、再帰でアプローチすると簡単です。

つまり、当然といえば当然ですが、問題というかデータ構造自体が再帰的な場合は、ループで考えることの方がややこしくなってきます。

(super-length '(1 2 3 (4 5 6 ((((((((7 8)9)10)))))))11))
;=> 11

;; 再帰で
(defun super-length (lst)
  (cond ((null lst) 0)
	((atom (car lst)) 
	 (+ 1 (super-length (cdr lst))))
	(:else 
	 (+ (super-length (car lst)) 
	    (super-length (cdr lst))))))

;; ループで
(defun super-length (lst)
  (prog (l stack len)
	(setq l lst)
	(setq len 0)
     L  (when (null l)
	  (if (null stack)
	      (return len)
	      (setq l (pop stack))))
	(if (atom (car l))
	    (incf len)
	    (push (car l) stack))
	(pop l)
	(go L)))

;; ループで その2
(defun super-length (lst)
  (loop :with l := lst :and stack :and len := 0
        :if (null l)
           :if (null stack)
              :return len
           :else
              :do (setq l (pop stack))
           :end
        :else
           :if (atom (car l))
              :do (pop l) 
                  (incf len)
           :else
              :do (push (pop l) stack)
           :end
        :end))

;; 末尾再帰で
(defun super-length (lst &optional (len 0) stack)
  (if (null lst)
      (if (null stack)
	  len
	  (super-length (car stack) len (cdr stack)))
      (if (atom (car lst)) 
	  (super-length (cdr lst) (1+ len) stack)
	  (super-length (cdr lst) len (cons (car lst) stack)))))

以上、難しさは、問題のアプローチに左右されるものであり「再帰」という概念とは別個のものなんじゃないかなあと、適当に考えたことを書いてみましたが、なんとなく纏め切れずに終わります…。

ArcでL-99 (P18 指定した範囲を切り出す)

| 00:26 | ArcでL-99 (P18 指定した範囲を切り出す) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P18 指定した範囲を切り出す) - わだばLisperになる

今回は、リストの指定した範囲を切り出すというお題です。

CLでいうところのsubseqの作成ですね。

Arcもこの前までは、subseqだったんですが、cutって名前に変更になっちゃったみたいです。

マイナスのインデックスは、配列の後から数えた位置になります。また、配列のサイズより大きい数値でもエラーにはなりません。

(def slice (lst start end)
  (unless (<= 0 start end (len lst))
    (err "The bounding indices are bad for a sequence of length."))
  ((afn (lst acc cnt)
	(if (or no.lst (< end cnt))
	    rev.acc
	    (self cdr.lst
		  (if (<= start cnt end)
		      (cons car.lst acc)
		      acc)
		  (+ 1 cnt))))
   lst () 1))

(slice '(a b c d e f g h i k) 3 7)
;=> (c d e f g)

(cut '(a b c d e f g h i k) 3 7)  
;=> (d e f g)
; 0オリジン

(cut '(a b c d e f g h i k) 3 -3)
;=> (d e f g)

2008-02-22

kill-backward-up-list

| 23:33 | kill-backward-up-list - わだばLisperになる を含むブックマーク はてなブックマーク - kill-backward-up-list - わだばLisperになる

最近S式を書いていて、

(print (+ 3 3))

というのを、「やっぱりprintはいらないや」と、

(+ 3 3)

のように、外側の式を削除して、内側のものを外に括り出す操作をパっとできるようにしたいと思うようになりました。

それで自作しようかなとも思いましたが、確か以前にどこかでこういう拡張を目にした記憶があったので、改めて探してみたところ、そのものズバリなものをみつけました。

;; the Zmacs function `kill-backward-up-list':
(defun kill-backward-up-list (&optional arg)
  "Kill the form containing the current sexp, leaving the sexp itself.
A prefix argument ARG causes the relevant number of surrounding
forms to be removed."
  (interactive "*p")
  (let ((current-sexp (thing-at-point 'sexp)))
    (if current-sexp
	(save-excursion
	  (backward-up-list arg)
	  (kill-sexp)
	  (insert current-sexp))
	(error "Not at a sexp"))))

Zmacs(LispマシンのEmacs)にはこの機能があったそうで、それの再現だそうです。

ちょっと調べたところでは、LispWorksのエディタ(Zmacs系)にもこの関数はあるみたいですね。

それで、キーバインドですが、とりあえず、C-M-sh-Hに割り当てて暫く様子をみることに。

Zmacsには他にもお宝が眠ってる気がする!

2008-02-21

短絡演算子的and

| 17:07 | 短絡演算子的and - わだばLisperになる を含むブックマーク はてなブックマーク - 短絡演算子的and - わだばLisperになる

今回も、当たり前すぎる例です。

;; and
(and p x)(if p
         x
         nil)

殆どのLISP処理系では、andの返り値は真偽の2種類ではなくて、評価した値、もしくは偽となっています。

これの起源ですが、古参LISPハッカーのBill Gosper氏のウェブサイトによれば、

Was de-facto in charge of MacLisp
before Jon L. White (while Greenblatt was preoccupied).
(E.g., added the feature that AND and OR can return non-Booleans.)

Gosper氏が考案したとのこと。「これは俺が考えたんだ!」と主張する人も皆無だと思うので、多分本当なのでしょう(笑)

実際のところLISP1.5では、真偽値しか返しませんでしたが、MacLISPの前身の(とはいえ境界が曖昧)PDP-6 LISPのマニュアルを眺めると、ブール値以外の値も返すようになっているので史実的にもこの時期(1966年頃)のようです。

また、空リストがNILと同じシステムでは、

(if (null x)
    ()
    (cons (car x) ...))(and x (cons (car x) ...))

のようにも書けます。

なんとなく通っぽいですが、実際のところは真偽値としてのNIL -> 空リストとしてのNILを混ぜて書くことはあまりないようで、太古のコードから最近のコードまで、あまり見掛けることもありません。

ArcでL-99 (P17 指定した位置でリストを二分する)

| 15:57 | ArcでL-99 (P17 指定した位置でリストを二分する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P17 指定した位置でリストを二分する) - わだばLisperになる

今回は、指定した位置でリストを二分するというお題です。

但し、予め定義された述語を使わないこと、とのこと。

(split '(a b c d e f g h i k) 3)
;=> ((a b c) (e f g h i k))

(def split (lst n)
  (if (<= n 0) 
      lst
      ((afn (lst acc cnt)
	 (if (is 0 cnt)
	     (cons rev.acc (list cdr.lst))
	     (self cdr.lst (cons car.lst acc) (- cnt 1))))
       lst () n)))

2008-02-20

ArcでL-99 (P16 周期Nで該当した要素を除外)

| 14:18 | ArcでL-99 (P16 周期Nで該当した要素を除外) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P16 周期Nで該当した要素を除外) - わだばLisperになる

今回は、指定した周期で要素を除外するというお題です。

(drop '(a b c d e f g h i k) 3)
;=> (a b d e g h k)

(def drop (lst n)
  ((afn (lst acc (o cnt 1))
     (if no.lst
	 rev.acc
	 (if (is cnt n)
	     (self cdr.lst acc)
	     (self cdr.lst (cons car.lst acc) (+ 1 cnt)))))
   lst () ))

;; 繰り返しで
(def drop (lst n)
  (let acc ()
    (forlen i lst
      (unless (is 0 (mod (+ i 1) n))
	(push lst.i acc)))
    rev.acc))

2008-02-19

UtiLispでL-99 (P15 各要素を任意の回数複製)

| 15:52 | UtiLispでL-99 (P15 各要素を任意の回数複製) - わだばLisperになる を含むブックマーク はてなブックマーク - UtiLispでL-99 (P15 各要素を任意の回数複製) - わだばLisperになる

何でか知りませんが、急にUtiLispでも書いてみたくなりました。

;; UtiLisp
(defun repli (lst times)
  (mapcan lst #'(lambda (x) (make-list times x))))

(defun make-list (n (elt nil))  
  (and (0> n) (err:argument-type n 'make-list))
  (do ((n n (1- n))
       (res () (cons elt res)))
      ((0= n) res)))

UtiLispならでは、っぽいところ

  1. map系の引数の順番が逆
  2. 0=は、zerop(zeropもある)
  3. 0>は、minusp、同様に0<はplusp
  4. (do (a b c) (t) )という書き方はNG。(do ((a) (b) (c) ) (t) )ならOK。
  5. ラムダリストでは、括弧で囲めば、オプショナル引数となり、デフォルト値の指定も可能
  6. error系が素朴でMacLISPっぽい。

ArcでL-99 (P15 各要素を任意の回数複製)

| 15:02 | ArcでL-99 (P15 各要素を任意の回数複製) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P15 各要素を任意の回数複製) - わだばLisperになる

前回は2つずつにするというお題でしたが、今回は、ちょっと発展して任意の回数繰り返したリストを返せというお題です。

(repli '(a b c) 6)
;=> (a a a a a a a b b b b b b b c c c c c c c)

(def repli (lst times)
  ((afn (lst acc cnt)
     (if no.lst
	 rev.acc
	 (if (is 0 cnt)
	     (self cdr.lst (cons car.lst acc) times)
	     (self lst (cons car.lst acc) (- cnt 1)))))
   lst () times))

;; 繰り返しで
(def repli (lst times)
  (mappend [newlist times _] lst))

(def newlist (n (o elt nil))
  (let acc ()
    (repeat n (push elt acc))
    acc))

LISPとリフレクション

| 02:20 | LISPとリフレクション - わだばLisperになる を含むブックマーク はてなブックマーク - LISPとリフレクション - わだばLisperになる

久々に2chのLispスレを眺めていて、3-LISPという処理系があるということを知りました。

3-LISPって一体何物と思って調べてみたら、リフレクションという概念が1982年にBrian Smith氏によって提唱された時に、その処理系として登場したとのこと。

とりあえず、面白そうだったので、処理系が公開されていないか探してみましたが、ぱっとしたものは見付からず。

それ以前にリフレクションが何だか分からなかったので、調べてみたところ、自己反映機能とも呼ばれていて、処理系自身で機能を拡張したり、実行速度を改善したりする機能のこと、とのこと。また、マクロはコンパイル時のリフレクションとも考えられる、という記述も。

何がなにやら、という感じでしたが、適当にリフレクションでヒットする論文を読んでみたところ、Common Lisp的に身近なところでは、eval-when等やコンパイル時に最適化する機構もリフレクションの一種らしいです。あと、CLOSの機能の一部とか。

例えば、define-compiler-macroはまさにそういう感じで、コンパイル時の状況によって最適化したりするのに使うようです。

(defun plus (&rest args)
  (apply #'+ args))

(define-compiler-macro plus (&whole form &rest args)
  (case (length args)
    (0 "引数がないので関数は呼び出さないで0に置き換えたいところ")
    (1 (car args))
    (t form)))

(defun foo (n)
  (plus n 3))
;=> 6

(defun bar ()
  (plus))

(print (bar))
;=> "引数がないので関数は呼び出さないで0に置き換えたいところ"

これは、CLtL2のコード例ですが、上記ではコンパイル時に引数が0か1個と判定できるならば、関数を呼びださないで、即値に置き換えてしまいます。

何となく面白いと思ったので、自分でも馬鹿っぽい例を考えてみました。

かなり無理矢理なのですが、実行結果が速く出る方を選択するdef-fasterというものを定義して、fib-slowと、fib-fastという、2種類のfibから速い方をfibという名前で呼び出せるようにする、というものです。

こういうのもリフレクションと呼べるのでしょうか…。

工夫すれば、何だか色々できそうです。

それはさておき、3-LISPがどういうものだったのか知りたい!

;; 動作
(defun fib-fast (n &optional (a1 1) (a2 0))
  (cond ((zerop n) 0)
	((< n 2) (values a1 'fast))
	('T (fib-fast (1- n) (+ a1 a2) a1))))

(defun fib-slow (n)
  (if (< n 2)
      n
      (+ (fib (1- n))
	 (fib (- n 2)))))

(def-faster fib 
    (fib-slow 30) (fib-fast 30))

(fib 10)
;=> 55, FAST

;; 定義
(defmacro run-time (fn)
  `(+ (- (get-internal-run-time)) 
      (progn
	,fn
	(get-internal-run-time))))

(defmacro def-faster (name x y)
  `(setf (symbol-function ',name)
	 (if (< (run-time ,x) (run-time ,y))
	     #',(car x)
	     #',(car y))))

2008-02-18

マクロ

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

計算機科学の専門家のブログエントリにプログラムも禄に組めないおっさんが反応するというのも畏れ多いのですが、なんとなく…。

あろはさんのマクロについてのエントリを読んでなるほど、なるほど、と共感する一読者なのですが、ただ「Lisp みたいに,最初から高級な言語を使うと,むしろマクロの位置付けがわかりにくいのではないか…」というところだけ共感できなかったのですね。それでエントリを書いてみました。

しかし、結局マクロについて言いたいことは同じです、すいません(^^;

Lispでマクロというと、Common Lispのような伝統的マクロや、パターンマッチのマクロの等、色々あると思うのですが、Emacs Lispや、Common Lispのような伝統的マクロは、書いていてまさに

マクロというのは,略記法のことであり,それ以上でもそれ以下でもない.
コードのまとまりに名前を付けておいて,ひとつメタな立ち位置からコードを生成する.
計算能力自体は増えない.ただ,人間が書きやすく,読みやすくなる,それだけ.

という感じがして、個人的には十分に低級だと思うのですが、一般的にはどうなのでしょう。

例として取り上げられているラムダ計算もまさにLispのマクロ的操作だなと思えました。

ただ、自分は、いつも単純にS式を切ったり貼ったりしてるだけなので、そう思うのかもしれません。

マクロの書といえば、On Lispが有名ですが、その著者のPG氏はどう考えているかといえば、「タイプ量を減らすことがマクロのすべて」とも言ってたりするので、この人も結構、単純に捉えてるんじゃないかな、と勝手に想像しています。

ただ,人間が書きやすく,読みやすくなる,それだけ.

というのも非常に示唆に富んでいると思います。文脈を考慮しないで、ただテキストを置換しただけでは、往々にして人が意図したものとは違う動きになってしまい、なかなか制御することが難しいことになると思うのですが、Lispのマクロは、この辺を人が扱えるように最低限のお膳立てをしてくれて、「ただ単純にペタペタ切り貼りしてるだけ」の感覚を提供しているシステムなんじゃないのかなあと思うのです。

ただ、この点では、マクロのシステムだけで完結する問題でないことは確かで、Lispのように単純にS式を直接操作するようなもので無ければ、ユーザが、ペタペタ切り貼りすることも難しくなり、お気楽にマクロを書くことも難しいんじゃないかと思います。

また、「すぐに訳の分からない俺文法プログラムができる」というマクロへの批判が良くあるかと思いますが、文法があるようなないようなLispでは、書かれたマクロが新しい俺文法を持つとは限りません。

どっちかというと俺文法を持った個性の強い「略記法以上のもの」が批判の対象になることが多い気がするので、(Common LispのLOOPとか。)そういう意味では、Lispでも「略記法以上のもの」はそんなに好ましいものと思われていないのかもしれません。

つまり、無くても済むのに、敢えて作られた俺文法が批判されているわけですが、逆に確固とした文法がある言語では、言語の作法を熟知していないユーザが新しい俺文法を作ってしまわないことの方が難しいかもしれず、マクロがあっても禄なことが無いのかもしれません。

…という風に長々と脱線しつつ書いてみましたが、マクロって「略記法以上のもの」じゃないと自分も共感するエントリでございます。

家出する局所関数

| 15:05 | 家出する局所関数 - わだばLisperになる を含むブックマーク はてなブックマーク - 家出する局所関数 - わだばLisperになる

もうネタ切れなので、今回はイディオムってよりはただのネタです。

CL策定のためのメーリングリストの1981〜83年頃のメールを眺めててみつけたものです。

元は、レキシカルクロージャ万歳ということで、labelsから定義した関数を外に返してるという例なんですが、ちょっとアレンジしてみました。

;; CL
(mapcar (labels ((fib (n &optional (a1 1) (a2 0))
		   (cond ((zerop n) 0)
			 ((< n 2) a1)
			 ('T (fib (1- n) (+ a1 a2) a1)))))
	  #'fib)
	'(0 1 2 3 4 5 6 7 8 9 10))

;=> (0 1 1 2 3 5 8 13 21 34 55) 

;; Scheme
(map (letrec ((fib 
	       (lambda (n a1 a2)
		 (cond ((zero? n) 0)
		       ((< n 2) a1)
		       (else (fib (- n 1) (+ a1 a2) a1))))))
       (cut fib <> 1 0))
     (iota 11))

;=> (0 1 1 2 3 5 8 13 21 34 55)

他に

(define fib
  (letrec ((f (lambda (c a1 a2)
		(cond ((zero? c) 0)
		      ((< c 2) a1)
		      (else (f (- c 1) (+ a1 a2) a1))))))
    (cut f <> 1 0)))

なんてのも考えてみましたが、Schemeだと、

(define (fib n)
  (define (f c a1 a2)
    (cond ((zero? c) 0)
	  ((< c 2) a1)
	  (else (f (- c 1) (+ a1 a2) a1))))
  (f n 1 0))

と書けるので、なんの意味もないですね(笑)

ArcでL-99 (P14 各要素を2つずつに複製)

| 14:40 | ArcでL-99 (P14 各要素を2つずつに複製) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P14 各要素を2つずつに複製) - わだばLisperになる

リストの各要素を2回繰り返したリストを返せというお題です。

要素を2倍したリストをjoin(append)するのではなくて、ちょっと捻ってconsを2回することにしてみました。

(def dupli (lst)
  ((afn (lst acc)
     (if no.lst
	 rev.acc
	 (self cdr.lst
	       (let f [cons car.lst _]
		 f:f.acc))))
   lst () ))

(dupli '(a b c c d))
;=> (a a b b c c c c d d)

;; 短かく
(def dupli (lst)
  (and lst `(,lst.0 ,lst.0 ,@(dupli cdr.lst))))

2008-02-17

結合法則的変形

| 18:30 | 結合法則的変形 - わだばLisperになる を含むブックマーク はてなブックマーク - 結合法則的変形 - わだばLisperになる

今回も当たり前過ぎる例ですが、GLS氏もこれって美しいってどっかで言ってたような憶えがあります。

(cons (if p x y) z)(if p 
	 (cons x z)
	 (cons y z))

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

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

P09では、ランレングス圧縮の方法として同じものを最初にリストにして、その子リストの長さと、要素でリストを構成していましたが、そうではなしに先頭から直接リストを作成して行けというお題。

連続する要素ならば、カウンタを一つ進め、そうでないなら、要素を追加、というような方法になるでしょうか。

(def encode-direct (lst)
  ((afn (lst acc)
     (if no.lst
	 rev.acc
	 (self cdr.lst
	       (let a car.acc
		 (if atom.a
		     (if (is car.lst a)
			 (cons `(2 ,a) cdr.acc)
			 (cons car.lst acc))
		     (if (is car.lst cadr.a)
			 (cons `(,(+ 1 car.a) ,car.lst) cdr.acc)
			 (cons car.lst acc)))))))
   lst () ))

(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))

2008-02-16

3つ以上の引数の不等号

| 17:56 | 3つ以上の引数の不等号 - わだばLisperになる を含むブックマーク はてなブックマーク - 3つ以上の引数の不等号 - わだばLisperになる

良さげなフレーズを集めるといっても経験豊富でもなんでもない私が集めるのは、なかなか無理があるのですが、まあ、とりあえず…。

今回も、なあんだ、というようなものですが、個人的には前置記法の特色が出てる気がして結構好きなものの一つです。

;; 1
(< 1 2 3 4 5 6 7)
;=> t

;; 2
(<= 2 x 3)(and (<= 2 x) (<= x 3))

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

;; 3
(= 1 x y)(and (= 1 x) (= 1 y))

ArcでL-99 (P12 ランレングス圧縮されたものを復元する)

| 17:23 | ArcでL-99 (P12 ランレングス圧縮されたものを復元する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P12 ランレングス圧縮されたものを復元する) - わだばLisperになる

前回のP11は、圧縮するものでしたが、そのデータを復元するのがお題。

Arcでmake-listに相当するものが見当たらなかったので自作しました。一応、newstringからの類推で、newlistという名前に。

make-listは全然違う名前になって潜んでたりして…。

(def decode (lst)
  ((afn (lst acc)
	(if no.lst
	    rev.acc
	    (self cdr.lst
		  (cons (if (atom car.lst)
			    car.lst
			    (apply newlist car.lst))
			acc))))
   lst () ))

(def newlist (size (o elt nil))
  ((afn (cnt acc)
	(if (<= cnt 0)
	    acc
	    (self (- cnt 1) (cons elt acc))))
   size () ))

(decode '((4 a) b (2 c) (2 a) d (4 e)))
;=> ((a a a a) b (c c) (a a) d (e e e e))

2008-02-15

みにくいアヒルの子系演算

| 15:56 | みにくいアヒルの子系演算 - わだばLisperになる を含むブックマーク はてなブックマーク - みにくいアヒルの子系演算 - わだばLisperになる

「ハッカーのたのしみ」という延々とビット演算が載ってる本がありますが、多分好きな人にとっては堪らない本だと思うのですね。

私もこの本をブックオフで購入したのですが、内容的にはさっぱりです。

しかし、作者はこういうのが相当好きなんだなということは伝わってきて、こういう本って良いなあと思ってしまい買ってしまったのですね…。

LISPでもリスト処理などに、こういうのが沢山あるような気がするので、微力ながらも気になったものを集めてみようかなと思いました。

なんというか、LISPで生活してないと出てこないような発想のようなものを集められると良いなと思うのですが、私の実力的にはなかなか難しそうです。

今回は、昔のLispマシンのソースを読んでて、そういえばそうだよなあと思った例です。

;; 1
(+ 1 2 3 -4 5)(+ (- (+ 1 2 3) 4) 5) 

(+ a b c (- d) e)(+ (- (+ a b c) d) e) 

(+ (frob) -1 2 3 4 5)(+ (1- (frob)) 2 3 4 5)

;; 2
(* a b c (/ d) e)(* (/ (* a b c) d) e)

どっちかというとこういうのは掲示板で募集してみた方が良いのかもしれないですね。

連続ものにしたいとは思っているのですが、ネタのストックはあと2つ3つ位しかありません(笑)

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

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

前回は、

((4 a) (1 b) (2 c) (2 a) (1 d) (4 e))

という風に出力していましたが、今回は、

((4 a) b (2 c) (2 a) d (4 e))

のように出力せよ、との問題。1つの場合は、リストにしないで、アトム単体で表現するというわけですね。

ということで前回のを少し修正して終わり。

(def encode-modified (lst)
  ((afn (lst acc)
	(if no.lst
	    rev.acc
	    (self cdr.lst
		  (let n (len car.lst)
		    (cons (if (is 1 n) caar.lst `(,n ,caar.lst)) 
			  acc)))))
   pack.lst () ))

(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))

2008-02-14

Arcでcond

| 17:42 | Arcでcond - わだばLisperになる を含むブックマーク はてなブックマーク - Arcでcond - わだばLisperになる

どうもArcのifには我慢できなくなったのでマクロでcondを作ってみることにしました。

ちょちょっと作業をしてみたのですが、SLIMEに馴れ切った自分にはSLIMEの助けが無いとマクロが書けないことに、はたと気付いてしまいました。

つまりマクロの展開形が簡単に確認できないと無計画にマクロを書けなということですね…。

そういうわけで遠回りながら、まず簡単にmacex1の結果が見れるようにしてみました。

といっても、Emacsのlisp-eval-region関数をちょっといじっただけのものを作っただけです。

EmacsのArc-modeが待ち遠しい…。

(defun arc-mecex1-region (start end &optional and-go)
  (interactive "r\nP")
  (comint-send-string (inferior-lisp-proc) "(ppr (macex1 '")
  (comint-send-region (inferior-lisp-proc) start end)
  (comint-send-string (inferior-lisp-proc) "))\n")
  (if and-go (switch-to-lisp t)))

これで気休め程度はマクロが書きやすくなったので、condの作成

;; そのまんまバージョン
(mac cond body
  `(if ,@(mappend [list car._ `(do ,@cdr._)] body)))

;; elseも使えるバージョン
(mac cond body
  `(if ,@(mappend [list (let x _.0 (or is!else.x x)) `(do ,@cdr._)]
		  body)))

;; 動作
(cond (a b)
      (c d)
      (else e))
;マクロ展開 =>
;(if a (do b) c (do d) t (do e))

新しい構文も取り入れて書いてみましたが、ArcはどんどんPerl化して行っている気がする!

Gauche Night

15:49 | Gauche Night - わだばLisperになる を含むブックマーク はてなブックマーク - Gauche Night - わだばLisperになる

私は現在、引きこもりということもあり遠出するのはどうも億劫でGauche Nightに行こうかどうかずっと迷ってましたが、先日、もしまだチケットが買えるなら行こうと思いLoppiでチケットを購入することにしてみました。

番号からすると結構後の方だったので割とぎりぎりだったかもしれませんが、チケットは購入できました。

ということで、私もGauche Night観覧することにしました!

しかし、皆さん始発で帰るんですかね。

Schemerは沢山いる…ってのは当然なんですが、CLerはいないかなあ。

ArcでL-99 (P10 連続する要素をランレングス圧縮する)

| 15:24 | ArcでL-99 (P10 連続する要素をランレングス圧縮する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P10 連続する要素をランレングス圧縮する) - わだばLisperになる

前回に引き続き連続する要素を纏める系の問題ですが、前回のpackを使えば簡単です。

そして昨日、新しいArcが公開されました。

新しい構文が追加されたり、関数名が変更されたり。

といっても、Arc at 3 Weeksにも書いてあるアイディアなので、今回採用になった、というほうが良いでしょうか。

この時は、x.yとx:yですが、これが、x.yと、x!yってことになったみたいです。

list.1.2.3
;=> (1 2 3)
list!x!y!z
;=> (x y z)

なんとなく微妙。

しかし、この構文を使って書くとコードの見た目が、かなり変わって来ます。

今回は、折角なので、新しい書法で書いてみました。

括弧がどんどん無くなる…。

(def encode (lst)
  ((afn (lst acc)
	(if no.lst
	    rev.acc
	    (self cdr.lst
		  (cons `(,(len car.lst) ,caar.lst) acc))))
   pack.lst () ))

(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))

2008-02-13

LET、LAMBDA、PROGN色々

| 17:11 | LET、LAMBDA、PROGN色々 - わだばLisperになる を含むブックマーク はてなブックマーク - LET、LAMBDA、PROGN色々 - わだばLisperになる

毎度、誰も興味を持たないであろうというLISPについての非常に局所的な何の役にも立たない考察をしておりますが、今回は、ローカルなブロックと変数の束縛機構の変遷について調べたことを書いてみたいと思います。

現在では、大概のところはLETで、他は、用途に応じて他はPROGNを使用する、位の感じで落ち着いていると思いますが、この流れに辿り着くまでには、それなりに変遷がございました。

とりあえず、無駄に年表的に纏めてみました。

処理系機構備考
1962Lisp 1.5PROG LAMBDA PROG2
1966PDP-6 LISPPROG2拡張引数は7つ迄の制限
1973MACLISPPROGN導入
1973MACLISP3番目のDO形式導入
1977-1979Zetalispラムダリストパラメータ&aux導入
Zetalisplet導入lambdaのシンタックスシュガー
Zetalispprog拡張 prog*導入
Zetalispprog1導入
Zetalispクロージャ導入
1983Zetalisp/CLtagbody、block導入

とりあえず、上から順に追って行くと、最初は、PROGとLAMBDAとPROG2しかありませんでした。

当初LAMBDAは、ボディ部が暗黙のPROGNではないので、式は1つしかとれず、PROG2は式が2つ取れて最後(当然2番目)の値を返してました。現在は、2番目の式の値を返すという解釈がされてますが、式が2つ取れるよ、という意味だったのかもしれません。

この時点で、ローカル変数の束縛と順次進行のブロックにおいて使い勝手が良いのは、PROGだけという感じなので殆どの関数にPROGが使われてる感じです。

;; やたらとPROG時代 - みんなこんな感じ
(defun fib (n)
    (prog (a1 a2 tem)
	  (setq a1 1)
	  (setq a2 0)
       L  (cond ((< n 2) (return a1)))
	  (setq tem a1)
	  (setq a1 (+ tem a2))
	  (setq a2 tem)
	  (setq n (1- n))
	  (go L)))

それで、1966年頃、PDP-6 LISPになって、PROG2が7つまでの式を取れるように拡張されました。互換性のためか2番目の値を返すのはそのまま。UCI LISP等は、LAMBDAのボディが暗黙のPROGNになり、今のLETの用途で、LAMBDAも使われ始めました。

;; 変数束縛にLAMBDA - 1970年台後半位まで
(defun foo (lst)
  ((lambda (x y) 
     (list x y)
     ...
     ...)
   (car lst) (cdr lst)))

その後、1973年にPROGNが登場。割と遅いです。そして、3番目の形式のDOですが、

(defun foo (lst)
  (do ((x (car lst)) (y (cdr lst))) nil
    (list x y)))

のようなもので、今のLETと同じ目的で導入されました。余計なnilが付いてることを除けば、形はLETと全く同じです。

しかし、殆ど使ってるコードがないので、流行らなかった様子です。まだまだ、PROG使用率が高い。

そして、1977頃からLispマシン登場近辺で一気に色んなものが花開いちゃってるんですが、ラムダリストパラメータの&AUX、LET、PROG1、PROG*等が導入されています。

どうも、LETより、&AUXの登場の方が古いのか、初期のLispマシンのコードでは、

(defun foo (lst)
  (let ((x (car lst)) (y (cdr lst)))
    (list x y)))

よりも

(defun foo (lst &aux (x (car lst)) (y (cdr lst)))
  (list x y))

の方が多かったりします。

当時のLETは今のSchemeと同じくマクロで、LAMBDAに展開されていました。

また、LETが浸透するにつれ&AUXの存在意義も薄れて行った感じです。

それと、MACLISPでは、LETは、destructuring-bindのように分割代入可能でした。

PROGの拡張については、

(prog ((x 0) (y 1))
      (list x y))

のように初期値が設定できるようになり、LET*からの類推からかPROG*も登場。

そして、PROG1登場。それ以前は、2つの値の交換は、

(setq x (prog2 nil y (setq y x)))

のように書かれてましたが、素直に

(setq x (prog1 y (setq y x)))

と書けるようになり、この段階でPROG2は何のために存在するのか分からないものになった模様。ちなみにPSETQの登場は、もう少し後になります。

また、LETや、LAMBDAのクロージャ的用法ですが、Zetalispもダイナミックスコープなので、レキシカルスコープのように、そのままではクロージャにならないので、それ専門のCLOSUREという構文がありました。

プリミティブとしては、CLOSUREを使うのですが、EMACSのlexical-letのようなLET-CLOSEDというマクロがあり、これを使うと

(let-closed ((a 5) b (c 'x))
   (function (lambda () ...)))

という風にクロージャが作れました。Zetalispのクロージャについては、色々操作できる謎の関数が沢山ありclosure-variablesで、中身の変数を取得したり、set-in-closureで外から中身の値を設定したり、closure-functionで、関数を取り出したり、copy-closureで複製できたり、意味なく色んなことができました。

;; Lisp Machine Lisp / Zetalisp
(defun make-accumulator (init)
  (let-closed ((acc init))
    #'(lambda (n)
	(setq acc (+ acc n)))))

(setq A (make-accumulator 5.))
(funcall A 10.)
=> 15.

(closure-variables A)
=> acc

(closure-function A)
=> (lambda (n) (setq acc (+ acc n)))

;; クロージャの中の変数Aの値を取得
(symeval-in-closure A 'acc)
=> 15

;; クロージャの中身を書き換え
(set-in-closure A 'acc 100.)
=> 100.

;; 変わっちゃいました
(funcall A 10.)
=> 110.

そして、Common Lispが最初なのか、Zetalispが最初なのか良くわかりませんが、1983年頃になると、TAGBODYと、BLOCKが登場し、基本的な構文は、LETとTAGBODYとBLOCKの組み合わせで表現するという流れになって行き、PROGを使う意義も薄れ、現在に至ります。

…という風に、お馴染のLETに辿り着くまでに割と色々あったということが分かったような、分からないような…。

以上、何のまとまりもないんですが、ローカルブロックの変遷でした。

SERIESのドキュメント

| 11:59 | SERIESのドキュメント - わだばLisperになる を含むブックマーク はてなブックマーク - SERIESのドキュメント - わだばLisperになる

凄く有用そうなのに今一つ普及していない、SERIESパッケージ。

資料も実例のソースコードも非常に少ないというか、全然Google等でも検索に引っ掛からないのですが、みつけられるところでは、CLtL2の付録のドキュメントと、SERIESの配布物の中のs-doc.txt位でしょうか。

日本語チュートリアル的なドキュメントとしては、

があります。

あと、前にこのブログでも試したものを書いてみたりしてたのを思い出しました。

こっちは、私が書いたものなので、内容は保証できませんが…。

そんな感じなのですが、今日AI Memoを漁っていたら、作者のRichard C. Water氏の解説をみつけました。

AIメモの番号は、AIM-1082と、1083です。

1082は大体、添付のs-doc.txtと似たような内容で、1083は理論的な解説です。

SERIESは言語とは、特定の言語とは独立したものだそうで、Pascal版のSERIESも載ってたりしてます。

あと、あんまり関係ないですが、SERIESの前身のような、LetSというものも見付けました、こちらは、AIM-680aですが、なんとなくSERIESよりこっちの方が簡素で分かりやすい気がしないでもありません…。

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

| 11:38 | ArcでL-99 (P09 連続して現われる要素を纏める) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P09 連続して現われる要素を纏める) - わだばLisperになる

この辺から私の頭では結構考えないと解けなくなってまいります。

折角Arcなのでネストしたifを若干無理な感じで使ってみました。PG氏のソースを読むと、ネストしたifの場合は、述部以外は一文字字下げしてるみたいです。

'elseはどこかで、こういう風に書くと分かりやすいよ、ってのを見たので真似してみたんですが、なんか落着かない…。結局どんどんcondに近付いて行くような…。

(def pack (lst)
  (rev ((afn (lst acc tem) 
	     (if (no lst)
		  (cons tem acc)
		 (or (is (car lst) (car tem)) (no tem))
		  (self (cdr lst)
			acc
			(cons (car lst) tem))
		 'else
		  (self (cdr lst)
			(cons tem acc)
			(list:car lst))))
	lst () () )))

(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))

2008-02-12

ArcでL-99 (P08 連続して現われるリストの要素を圧縮する)

| 11:36 | ArcでL-99 (P08 連続して現われるリストの要素を圧縮する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P08 連続して現われるリストの要素を圧縮する) - わだばLisperになる

'(a a a a b c c a a d e e e e)というリストを、'(A B C A D E)という風に圧縮せよ、という問題です。

思いついたまま書いてみました。(nilを正しく扱っていなかったので、3/19修正)


(def compress (lst)
  ((afn (lst acc)
     (if no.lst
	 rev.acc
	 (self cdr.lst
	       (if (and (is car.acc car.lst) acc)
		   acc
		   (cons car.lst acc)))))
   lst () ))

(compress '("a" "a" "a" "a" a b b b c c c e a a d e))
;-> ("a" a b c e a d e)

2008-02-11

S式だった頃のDylan (2)

| 08:53 | S式だった頃のDylan (2) - わだばLisperになる を含むブックマーク はてなブックマーク - S式だった頃のDylan (2) - わだばLisperになる

ちょっと前のエントリでS式Dylanのマニュアルが見付けられない、ということを書きましたが、タイムリーなことに、comp.lang.lispで、同じようにS式Dylanのマニュアルを探してる、という方が、入手できる方法はないかを質問したところ、ほぼ即答で、ウェブからマニュアルを入手できるよ、との回答が!

昔は、AppleのftpでPS版も配布してたらしいんですが、それをHTML化したものでしょうか。

ということで、早速入手!

doが一般化された、forとか面白いです!

ArcでL-99 (P07 リストの中身を平板化する)

| 07:18 | ArcでL-99 (P07 リストの中身を平板化する) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P07 リストの中身を平板化する) - わだばLisperになる

平板化って言葉がなんだか良く分からないのですが、変換できたので使ってみます。

おなじみのflattenの作成ということのようです。

nilを要素とするか、空リストと見るかで動作が変ってしまうと思うんですが、どっちがメジャーな解釈なんでしょう。

とりあえず、nilは、要素とすることにしてみました。

Arcには、flatがあって、こっちは、nilはリストってことになるようです。

また、appendは、Arcでは+か、joinになります。

(def flatten (lst)
  (rev ((afn (lst acc)
	     (if (no lst)
		  acc
		  (self (cdr lst) 
			(if (atom (car lst))
			    (cons (car lst) acc)
			    (+ (self (car lst) () ) acc)))))
	lst () )))

(flatten '(1 2 (3 (4 5 (()(()(((((((6((((((7 8 9)))))))10)))))))))()) 11 (12)))
;-> (1 2 3 4 5 nil nil 6 7 8 9 10 nil 11 12)

(flat    '(1 2 (3 (4 5 (()(()(((((((6((((((7 8 9)))))))10)))))))))()) 11 (12)))
;-> (1 2 3 4 5 6 7 8 9 10 11 12)

2008-02-10

Getting Started in *LISP (4)

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

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

大分間があいてしまいました‥。

特に理由はないけれどこのチュートリアルは終らせたいところ。

1.2.3 Defining a More Complex Automaton

の続きで、One Small Step...for 9 Life

(defun one-step ()
  (*let ((count (neighbor-count)))
    (cond!!
      ;; 周囲の数が<1か、>3なら、1を引く
      ((or!! (<!! count 1) (>!! count 3))
       (if!! (zerop!! *automata-grid*)
	     *automata-grid*
	     (1-!! *automata-grid*)))

      ;; 2か、3なら1を足す
      ((<=!! 2 count 3) (1+!! *automata-grid*))

      ;; その他は、そのまま
      (t *automata-grid*))))

オートマトンの状態を一段階ずつ進める関数を定義

cond!!と、if!!は、pvarそれぞれを判定して結果を返す、SIMD版condとif

他にも色々ユーティリティを定義

(defun set-cells (cell-list value)
  (dolist (cell cell-list)
    (set-cell (car cell) (cadr cell) value)))

(defun init ()
  (set-grid 0)
  (set-cells '((2 2) (3 1) (3 2) (3 3) (4 1))
	     1)
  (view))

(defun view-step (&optional (n 1))
  (run n)
  (view))

実行してみる

(init)
;
;     DIMENSION 0 (X)  ----->
;
;0 0 0 0 0 0 0 0 
;0 0 0 1 1 0 0 0 
;0 0 1 1 0 0 0 0 
;0 0 0 1 0 0 0 0 
;0 0 0 0 0 0 0 0 

(view-step)
;     DIMENSION 0 (X)  ----->
;
;0 0 0 0 0 0 0 0 
;0 0 1 2 1 0 0 0 
;0 0 1 2 1 0 0 0 
;0 0 1 1 0 0 0 0 
;0 0 0 0 0 0 0 0 

(view-step 45)  ...
;
;     DIMENSION 0 (X)  ----->
;
;0 0 0 0 0 0 0 0 
;0 0 0 8 3 0 0 0 
;0 0 5 0 7 0 0 0 
;0 0 4 5 9 0 0 0 
;0 0 0 0 0 0 0 0 

しかし、これだと、おなじ升目から動かない。

→ノイマン型だから

;; moore型に変更
(setq *neighborhood* :moore)
(init) 
;
;     DIMENSION 0 (X)  ----->
;
;0 0 0 0 0 0 0 0 
;0 0 0 1 1 0 0 0 
;0 0 1 1 0 0 0 0 
;0 0 0 1 0 0 0 0 
;0 0 0 0 0 0 0 0 

(view-step) 
;
;     DIMENSION 0 (X)  ----->
;
;0 0 0 1 1 0 0 0 
;0 0 1 2 2 0 0 0 
;0 0 2 0 0 0 0 0 
;0 0 1 2 1 0 0 0 
;0 0 0 0 0 0 0 0 

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

ムーア型だと大分ばらける

ArcでL-99 (P06 リストの中身が回文的かを調べる)

| 07:07 | ArcでL-99 (P06 リストの中身が回文的かを調べる) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P06 リストの中身が回文的かを調べる) - わだばLisperになる

今回は、リストの内容が回文になっているかどうかを調べるというもの、ひっくりかえして比較すれば良いのですが、一応練習ということで丁寧に書いてみました。

同じ要素で構成されたリストかを比較するので、isoを使っています。

(def palindrome (lst)
  ((afn (l acc)
	(if (no l)
	    (iso lst acc)
	    (self (cdr l) (cons (car l) acc))))
   lst () ))

;; 簡単に
(def palindrome (lst)
  (iso (rev lst) lst))

(palindrome '(x a m a x))
;-> t

(palindrome '(た け や ぶ や け た))
;-> t

2008-02-09

EmacsのCommon Lispインデント

| 04:02 | EmacsのCommon Lispインデント - わだばLisperになる を含むブックマーク はてなブックマーク - EmacsのCommon Lispインデント - わだばLisperになる

EmacsでCommon Lispのコードを編集する時には、cl-indentを使用しているのですが、割とprogと、loopの時に思ったようにインデントしてくれないので、色々調整してみているのですが、どうも簡単な方法がみつからず結局、cl-indent.elの関数を変更することにしてみました。とはいえ、後で自作の関数を読み込みさせるだけですが…。

どういう風にインデントされて欲しいかというと、

(loop :for i = 0 :then (incf i)
      :if (< 10 i)
	:do (print "End.")
            (return res)
      :else
	:collect i :into res
      :end)

(prog (foo)
      (setq foo '(foo bar baz))
   L  (cond ((endp foo) (return)))
      (pop foo)
      (go L))

みたいな感じです。まあ、いまどきPROG使う人もいないと思うんですが、PROGの時はずらっと括弧の位置が揃ってると気持ち良いんですよね。

LOOPはとりあえず、2行目以降、7カラム目に揃ってれば良いんですが、doのときに、2つ字下げしてるのをLispマシンのマニュアルで発見したので、そういう風なのも良いかなと。

とりあえず、無理矢理な感じですが、設定をでっち上げました。

;; Common Lisp - インデント関係の設定
;; tagbody / prog
(setq lisp-prog-tag-indentation 3
      lisp-prog-tag-body-indentation 6
      lisp-tag-indentation 1
      lisp-tag-body-indentation 3)

;; lisp-indent-tagbodyをほんのちょっと改造
(defun lisp-indent-prog (path state indent-point sexp-column normal-indent)
  (if (not (null (cdr path)))
      normal-indent
    (save-excursion
      (goto-char indent-point)
      (beginning-of-line)
      (skip-chars-forward " \t")
      (list (cond ((looking-at "\\sw\\|\\s_")
                   ;; a tagbody tag
                   (+ sexp-column lisp-prog-tag-indentation))
                  ((integerp lisp-prog-tag-body-indentation)
                   (+ sexp-column lisp-prog-tag-body-indentation))
                  ((eq lisp-prog-tag-body-indentation 't)
                   (condition-case ()
                       (progn (backward-sexp 1) (current-column))
                     (error (1+ sexp-column))))
                  (t (+ sexp-column lisp-body-indent)))
            (elt state 1)))))

(put 'prog 'common-lisp-indent-function 'lisp-indent-prog)

;; loop
(defun common-lisp-loop-part-indentation (indent-point state)
  "Compute the indentation of loop form constituents."
  (let* ((loop-indentation (save-excursion
			     (goto-char (elt state 1))
			     (current-column))))
    (goto-char indent-point)
    (beginning-of-line)
    (cond ((looking-at "^\\s-*:?\\(do\\|collect\\)\\(\\s-\\|\(\\)\\(\\s-*\\|;\\)")
	   (+ loop-indentation lisp-loop-keyword-indentation 2))
	  ((looking-at "^\\s-*\\(:?\\sw+\\|;\\)")
	   (+ loop-indentation lisp-loop-keyword-indentation))
	  (t
	   (+ loop-indentation lisp-loop-forms-indentation))))))

(setq lisp-loop-forms-indentation 6
      lisp-loop-keyword-indentation 6
      loop-indentation 6)

(put 'loop 'common-lisp-indent-function 'common-lisp-loop-part-indentation)

;; (cl-indent 名前 手本)
(defun cl-indent (sym indent)
  (put sym 'common-lisp-indent-function
       (if (symbolp indent)
	   (get indent 'common-lisp-indent-function)
	 indent)))

(cl-indent 'iterate 'let)
(cl-indent 'collect 'progn)
(cl-indent 'mapping 'let)
(cl-indent 'define-syntax 'let)

PROGは、まあまあこれでできたのですが、LOOPが

(loop :for i = 0 :then (incf i)
      :if (< 10 i)
	:do (print "End.")
      (return res)
      :else
	:collect i :into res
      :end)

のようになるので、doの範囲を自分で字下げしないといけないという…。あと、lisp-indent-lineでは字下げしてくれますが、indent-regionとか、indent-sexpでは、doとかcollectは字下げしてくれないので、これも面倒だという。

何か簡単に調整してくれるEmacs Lispとか配布されてないんですかねー。

皆さんはどうされているのでしょう。

その時代のパラダイム的なものとLISP

| 01:50 | その時代のパラダイム的なものとLISP - わだばLisperになる を含むブックマーク はてなブックマーク - その時代のパラダイム的なものとLISP - わだばLisperになる

プログラミングの初心者がこんなこと考えていて、またブログに書いてもしょうがないとは思うのですが、ふと、現在大多数の人が考えているLISP像というは、謂わば、Schemeのことなんじゃないかなと、思えてきました。

なんというか、今となっては、Unixといえば、Linuxのこと、みたいな、そんな感じで。勿論私は、Unixといえば、Linuxとは思ってないですが(笑)

レキシカルスコープじゃないLISPとか、GO TOがあったりとか、ガンガン代入文を使ったスタイルとか、それと、ちょっと違うかもしれないけれど、見た目がS式じゃなかったりとか。

そういうのは大多数の人にとってLISP的じゃないんじゃないかなと。

  • レキシカルスコープ

レキシカルスコープに関しては、Schemeが持ち込んで、その後のCommon Lispにも採用されました。

その前(1980年代初頭)は、ダイナミックスコープの処理系が主流で、MACLISP、Zetalisp、Franz Lisp(勿論Allegro CLじゃないです)等、ダイナミックスコープです。ただ、コンパイルした関数は、レキシカルスコープだったりしますが…。

  • GO TO

GO TOについては、これも70年代後半位から、GO TOの機能を提供していたPROGの使用が減少し、CLの登場で決定的になりました。ちなみにGO TOは後から取り入れられたものではなく、最初から存在します。

これについては、構造化プログラミングスタイルの啓蒙ってことも大きいとは思うのですが、その他には、PROGの果して来た機能が徐々に分解されて提供されるようになったということもあると思います、具体的には、progn、do、let、tagbody、block、等に分解されて行ったように見えます。これ以前のスタイルでは、lambdaとprogの組み合わせが主なものだったようです。主義というよりも、なんとなくPROGで書くより便利なので、そっちに乗り換えられていったという気がします。

構造化プログラミングスタイル云々より以前からLISPはあった訳なので、この辺はなんとなく独特な気がします。

また、GOTOとはいえ、関数内に限られているので、そんなに飛べる範囲は広くないので、そんなに恐しいことにはならないというのもあるでしょう。とはいえ、1つの定義で500行の壮観なGO TO多用型の関数もあったりしますが(笑)

  • ガンガン代入文を使ったスタイル

ガンガン代入文を使ったスタイルについては、割とCommon Lispでも見られると思いますが、Schemeじゃ好かれないですよね(笑)

  • プログラムの書式がS式じゃない

比較的ユーザに近い層で、S式にマッピングされたり、根底の考えがS式なのであれば、やっぱりLISPっぽくなる気がします。Dylanとか、やっぱりLISPな気がしますし、M式とか、CGOLとかユーザが直接S式を書かない方式もありました。

そういう意味で、Matz Lispは、LISPっぽいのかどうか、興味があります。自分はろくにRubyは読み書きできないのでなんとも言えないのですが、S式を感じられるなら、LISPなんじゃないでしょうか。今の私のレベルでは、あんまり感じないんですけども…。

それで何が言いたかったかということなのですが、書きたいように書けるのがLISPの良いところなんじゃないかなあ、と。

BASICみたいなスタイルで書いても良いし、SmallTalkみたいに書いても良いし、Schemeみたいに書いても良いし、Perlみたいに書いても良いし、個人のスタイルを変更しないで書くことが楽だと思います。

極論すれば、LISPには文法がないですが、限定されたスタイルもない、みたいな。

という、なんともまとまりのないエントリですいません。

もちろん、私は趣味プログラマだから、この考えは成り立つんだと思います。

2008-02-08

ArcでL-99 (P05 リストの中身を逆転させる)

| 21:45 | ArcでL-99 (P05 リストの中身を逆転させる) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P05 リストの中身を逆転させる) - わだばLisperになる

今回は、「リストの中身を逆転させる」ということで、CLでいうとreverseの作成ですね。

Arcには標準で、revがあります。

(def reverse (lst)
  ((afn (org acc)
	(if (no org)
	    acc
	    (self (cdr org) (cons (car org) acc))))
   lst () ))

(reverse '(foo bar baz))
;=> (baz bar foo)

(rev '(foo bar baz))
;=> (baz bar foo)

2008-02-07

S式だった頃のDylan

| 10:23 | S式だった頃のDylan - わだばLisperになる を含むブックマーク はてなブックマーク - S式だった頃のDylan - わだばLisperになる

Lisp系言語でS式でないものといえば、Dylanが有名ですが、Dylanも当初はS式だったとのこと。

前々からS式だった頃のDylanってどんなものだったのか興味はあったのですが、Googleでも全然ヒットしないし、全く未知のものでした。

そんな今日、暇だったので、CMUのAIレポジトリを眺めていたのですが、Dylanのコーナーがあったのをみつけ、ファイルを漁っていたら、一番最初の古いマニュアルの中のサンプルコードが纏められているファイルをみつけました。

このexample.txtファイルでは、DylanがS式で記述されています。

ぱっと見たところでは、7割Scheme+3割CLOSのような不思議な感じですが、S式が好きな自分にとっては、結構良い感じなのでAlgol風になっちゃったのは非常に残念だったなあと思いました。マクロの仕様が決定したのは、Algol風になってからっぽいですが、Dylanのマクロは衛生的マクロなので、S式だったら、オブジェクト指向が強調されたSchemeって感じになってたんでしょうか。

それでまた全然関係のない方向に脱線していったのですが、当時Dylanにもcondがあって、デフォルト節がelse:ってことになってるのをみつけました。

Dylanでは、else:のような表記は、キーワードになり、Common Lispでいうと、:elseになります。

;; Dylan
(cond ((< new-position old-position)
        "the new position is less")
      ((= new-position old-position)
        "the positions are equal")
      (else: "the new position is greater"))

それで、考えてみれば、Common Lispでもデフォルト節は、非NILだったらなんでも良いので、

;; Common Lisp
(defun fib (n)
  (cond ((< n 2)
	 n)
	(:else
	 (+ (fib (1- n))
	    (fib (- n 2))))))

(defun fib (n)
  (cond ((< n 2)
	 n)
	(:default
	 (+ (fib (1- n))
	    (fib (- n 2))))))

でも大丈夫だなと。

結構ナイスなんじゃないかと思ったので、きっと他にもやってる人がいるに違いないと思い、Google Code Searchでも検索してみました。

予想通り同じことを考えてる人はいて、condに:elseを使ってる人は非常に少数ながら何人か見付けられました、が、しかし、年代が、大体1990年付近のコードばっかりなので、もしかしたら、Dylanの影響なのかもしれないなと、思ったり、思わなかったり。

ArcでL-99 (P04 リストの要素の個数を数える)

| 04:33 | ArcでL-99 (P04 リストの要素の個数を数える) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P04 リストの要素の個数を数える) - わだばLisperになる

今回は、「リストの要素の個数を数える」のがお題ということで、要するに、lengthの作成ですね。

Arcでは、lengthはlenとなっていて、リスト/文字列/テーブルが引数に取れるようです。

;; 動作
(list-len '(foo bar baz))
;=> 3

;; 定義
(def list-len (lst)
  ((afn (lst cnt)
	(if (no lst)
	    cnt
	    (self (cdr lst) (+ cnt 1))))
   lst 0))

;(len '(foo bar baz))
;=> 3

2008-02-06

日本語プログラミングとLISP

| 01:18 | 日本語プログラミングとLISP - わだばLisperになる を含むブックマーク はてなブックマーク - 日本語プログラミングとLISP - わだばLisperになる

今日は、お茶を飲みながら、「初心者に優しい」ということと、LISPについて考えていました。

FORTHなどのスタック言語の文法と日本語文法とは非常に相性が良いことは良く知られていると思うのですが、LISPもリスト構造をひっくりかえしてしまえば、スタック言語の構造に非常に近い見た目になるかと思います。

そんなこんなで初心者に優しい日本語LISPのことを考えていたら、どういう訳か谷川俊太郎の詩が頭に浮んで来たので、そのインスピレーションをそのままLISPのコードに叩きつけてみました。

当初は、もう少し野心的な作りだったのですが、割とすんなり動かず、デバッグしているうちに、なんだかどんどん悲しくなってきたので、簡単なところで切り上げました。

;; 動作

;; 関数定義など
(with-俊太郎
  (("とがつてる" "月へゆくロケツトそつくり" というリスト)というのは公には おちんちん)

  (((100 通りのあてずっぽう)90 より大きいですか?)というのが おにがめかくし)

  ((((とべ とべ)
     ((おちんちん というのはどんなもの?)を 書け)
     (おにがめかくし)してるまに)
    とべ とべとべ)
   というのが 男の子のマーチ))

;; 定義した関数の実行
(男の子のマーチ)
; =>
;("月へゆくロケツトそつくり" "とがつてる") 
;("月へゆくロケツトそつくり" "とがつてる") 
;("月へゆくロケツトそつくり" "とがつてる") 
;("月へゆくロケツトそつくり" "とがつてる") 
;("月へゆくロケツトそつくり" "とがつてる") 
;("月へゆくロケツトそつくり" "とがつてる") 

;; 定義
(defpackage :俊太郎 (:use :cl))

(in-package :俊太郎)

(defun super-reverse1 (lst acc)
  (cond ((atom lst) acc)
	((atom (car lst)) 
	 (super-reverse1 (cdr lst) (cons (car lst) acc)))
	('T (super-reverse1 (cdr lst) (cons (super-reverse1 (car lst) () ) acc)))))

(defun super-reverse (lst) (super-reverse1 lst () ))

(defun super-remove (item lst)
  (remove item
	  (mapcar (lambda (x)
		    (if (consp x) (super-remove item x) x))
		  lst)))

(setf (symbol-function '通りのあてずっぽう) #'cl:random
      (symbol-function '書け) #'cl:print
      (symbol-function 'より大きいですか?) #'cl:>
      (symbol-function 'というリスト) #'cl:list
      (symbol-function 'してるまに) (macro-function 'cl:when)
      (symbol-function 'というのが) (macro-function 'cl:defun)
      (symbol-function 'というのはどんなもの?) (symbol-function 'cl:values)
      (symbol-function 'とべ) (symbol-function 'cl:go))

(defmacro というのは公には (var val) `(defparameter ,var ,val))
(defmacro とべとべ (&body body) `(prog () ,@body))

(defmacro with-俊太郎 (&body body)
  `(progn
     ,@(mapcar (lambda (x) (s->cl (super-reverse x)))
	       body)))

(defun s->cl (expr)
  (let ((expr (remove-てにをは expr)))
    (cond 
      ;; defun
      ((eq 'というのが (cadr expr))
       `(,(cadr expr) ,(car expr) nil ,@(cddr expr)))
      ;; defparameter
      ((eq 'というのは公には (cadr expr))
       `(,(cadr expr) ,(car expr) ,@(cddr expr)))
      ('T expr))))

(defun remove-てにをは (expr)
  (reduce (lambda (res x) (super-remove x res))
	  '(て に を は で)
	  :initial-value expr))
;

2008-02-05

LISP-2とFUNCALL

| 01:05 | LISP-2とFUNCALL - わだばLisperになる を含むブックマーク はてなブックマーク - LISP-2とFUNCALL - わだばLisperになる

del.icio.us等のArc関連でブックマークされた記事を眺めていると、ArcがLISP-1で伝統的なマクロを採用したということが話題になっているようです。

確かに名前の競合に注意を払わなくてはいけないので、面倒という気がしますが、どれだけ大変になるものなのでしょう。

書いてはいけないパターンの一覧とかあると良いなあと思うのですが…。

それで、そういうことを考えていて、どんどん脱線してしまい、どういう訳かLISP-2のFUNCALLについて調べていました。

このFUNCALLですが、LISP-2のCommon Lispや、Emacs Lispではお馴染だと思います。

それで、導入されたのは、LISP OARCHI*1というMACLISPについての文献によれば、1973/9/15付けの記録で新しい関数の導入として説明されているので、1973年のようです。

導入の説明としては、

((CAR X) A B C)

と書くのも

(FUNCALL (CAR X) A B C)

と書くのも同じという説明があります。ただ、

((LAMBDA (CAR) (CAR X)) 'FOO)

のような場合、関数属性をもったシンボルだと、そっちが優先されてしまうので、意図した結果になりません。それで、

((LAMBDA (CAR) (FUNCALL CAR X)) 'FOO)

という風に書くことによって回避することができるようになる、と記録されています。

同じLISP-2である、MACLISP系のEmacs Lispでも、Common Lispでも、

((LAMBDA (CAR) (CAR X)) 'FOO)

という記述は意図した結果になりませんが、MACLISPでは、

('car '(1 2 3 4))

(#'car '(1 2 3 4))

((if t 'car 'cdr) '(1 2 3 4))

共にOKです。こういう記述をするとなんとなく見た目的にLISP-1に近いですが、

(let ((foo #'car))
  (foo '(1 2 3 4)))

は変数属性に値が入り、関数属性は空なのでエラーです。(fooが予め他で関数属性を持つ定義をされていれば、そっちが呼び出されます。)

MACLISPの場合を纏めると、

  1. リストの先頭を見て、アトムならばそのシンボルが関数属性を持つか調べ、あったらそれ以降を引数として、関数として実行。もしくはオブジェクトによりエラーとする。
  2. コンスならば、その式を評価して、結果を評価。

ということになるかと思います。

うまくオチが全く付けられないのですが、LISP-2にFUNCALLは必須というわけではなかったのか!ということに意味なく感動したので、このエントリを書いてみたのですが、どうにも纏まりませんでした…。

ちなみに、MACLISPでは、マクロ、コンパイル済み関数、依存ファイルのオートロード属性等々色々な属性がシンボルに付けられていて、それによって評価器が色々判断して動作していて、単純で分かりやすい気もします。Common Lispでは、この辺の動作は大分違っています。

*1:MITのAIラボのITSという当時メインで使われていたOS上のファイル

2008-02-04

idfnとidentityとcr

| 02:37 | idfnとidentityとcr - わだばLisperになる を含むブックマーク はてなブックマーク - idfnとidentityとcr - わだばLisperになる

ArcではCommon LispやSchemeのidentityが、idfnという名前になりました。

どうせなら、idくらい短かくなってしまえば良いのになどと思ってしまいますが、そんなに頻繁に使うわけでもないので、4文字位に収まっているのでしょう。

このidfnという省略名を見て思い出したのが、HHKでお馴染の和田英一先生の和田研究室が1980年代に開発したUtiLispのCRという関数。

このCRは、CAR、CDRと同じ系統のものです。

CARが1番目の要素を参照し、CDRがその残り…ということをC...Rと書けるならば、0番目を参照するということはCRと書けるんじゃないか、それで、0番目が指すものは何だってことになると、そのオブジェクト自身のことだ、ってことなのでしょうか。

それで、このCR関数なのですが、UtiLisp特有の物かと思っていたら、UNIVAC 1100 Lispという1960年代後半にUNIVAC 1100というメインフレームに移植されたLISP 1.5系のLispにも存在していました。

UtiLispは、UNIVAC 1100 Lispから影響を受けたのか、それとも、LISP 1.5にはそういう方言があったのか、はたまた、両者無関係にC...Rの類推から思い付いたのか、知る由もありません(笑)

※また、この上なくどうでも良いことなのですが、このUNIVAC 1100 LispにはDOがあるのですが、これは、Arcdoと全く同じもので、40年前に先輩がいたというのも面白い。

1960年代位までのCAR、CDR関数の捉えられかたは、今のFIRSTと、REST関数と同じもの、というものとは、ちょっと違っていたんじゃないのかなあと、個人的には思っています。

例えば、PDP-1 LISPでは、シンボルのCDRには、プロパティが収められていて、関数の場合は、関数の定義が入っていますし、PDP-6 LISPでは、それに加えて、シンボルのCARは、何に使うのか良く分かりませんが、オブジェクトのアドレス -1の数値です。何というかもっとアドレス演算子っぽい雰囲気があるんですが、CRもそういうところから出てきた気がしています。

ちなみに、UtiLispの最新版Utilisp/C 1.14は、ソースが公開されていて、32bit Linuxでさっくり動作します。綺麗なマニュアルも付いてきますので、是非お試しあれ!

2008-02-03

ArcでL-99 (P03 リストのK番目の要素を取り出す)

| 23:33 | ArcでL-99 (P03 リストのK番目の要素を取り出す) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P03 リストのK番目の要素を取り出す) - わだばLisperになる

P03はリストのK番目の要素を取り出すものを作成せよとのこと。

CLでは、nthや、elt、Arcだと、(lst idx)という風に直接指定できますが、勉強ということで再帰的定義で。

;; L-99 (3)
;; 定義
; 1オリジンで勘定せよとのこと
(def elt-at (lst idx)
  (if (is idx 1)
      (car lst)
      (elt-at (cdr lst) (- idx 1))))

;; 動作
(elt-at '(a b c d e) 3)
;=> c

;; 別解
(def elt-at (lst idx)
  (lst (- idx 1)))

リーダーマクロも深すぎる

| 02:01 | リーダーマクロも深すぎる - わだばLisperになる を含むブックマーク はてなブックマーク - リーダーマクロも深すぎる - わだばLisperになる

Arcについてのブログ記事で、Common Lispのリーダーマクロでhash.keyとか、(hash => key)とか、(hash key)とかできるのか否か、という話題がありました。

面白そうだったので、自分もあれこれ考えて挑戦してみることにしました!

とりあえず、全然関係ないですが、Arcの[ _]構文から、

;; 動作
(mapcar [* 3 _] '(1 2 3 4))
;=> (3 6 9 12)

;; 定義
(set-macro-character #\[ 
		     (lambda (stream char)
		       (declare (ignore char))
		       (let ((body (read-delimited-list #\] stream t)))
			 `#'(lambda (,(intern "_")) ,body))))

(set-macro-character #\] (get-macro-character #\)))

次に、

  1. hash.key
  2. (hash => key)
  3. ("string" 1)
  4. ('(foo bar baz) 1)

のような記法。

本当は丸括弧にしたいところですが、{}で囲んでごまかしています。

丸括弧にも関数は対応しているわけなので、その関数を拡張すれば良いんだろうなと、なんとなくの見当は付いた気はしますが、荷が重いので、今後の課題とすることにしました…。本当に果てしないなあ…。

;; 動作
(let ((l '(foo bar baz))
      (ht (make-hash-table)))
  (setf (gethash 'key ht) 'val)
  (list
   {"foo" 1}
  {'(foo bar baz) 2}
  {l 2}
  {ht 'key}				;(ht 'key)
  {ht => key}				;(ht => key)
  {progn
     ht.key})) 				;ht.key
;=> (#\o BAZ BAZ VAL VAL VAL)

;; ごみごみとした適当な定義
(set-macro-character #\} (get-macro-character #\)))

(set-macro-character #\{
		     (lambda (str char)
		       (declare (ignore char))
		       (let ((body (read-delimited-list #\} str t)))
			 (cond ((eq '=> (cadr body))                 ; (hash => key)
				`(obcall ,(car body) ',(caddr body)))
			       ((some #'sym.bol-p body)              ; hash.key
				(mapcar (lambda (x)
					  (if (sym.bol-p x)
					      (hash.key->gethash x)
					      x))
					body))
			       ('T `(obcall ,@body))))))

(defun sym.bol-p (sym)
  (and (symbolp sym)
       (position #\. (string sym)) t))
      
(defun obcall (obj arg)
  (etypecase obj
    (hash-table (gethash arg obj))
    (sequence (elt obj arg))))

(defun hash.key->gethash (sym)
  (let* ((str (string sym))
	 (sep (position #\. str)))
    (flet ((intern-upcase (str) (intern (string-upcase str))))
      `(gethash ',(intern-upcase (subseq str (1+ sep)))
		,(intern-upcase (subseq str 0 sep))))))
;

2008-02-02

ArcでL-99 (P02 最後の要素をリストにして返す)

| 18:45 | ArcでL-99 (P02 最後の要素をリストにして返す) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P02 最後の要素をリストにして返す) - わだばLisperになる

P01をリストでくるんで終了

;; L-99 (2)
;; last-cons
(def last-cons (lst)
  (if (atom:cdr lst)
      (list ((if (dotted lst) cdr car) lst))
      (last-cons:cdr lst)))

;; 動作
(lasT '(foo bar baz))                   ;-> (baz)
(lasT '(foo . bar))                     ;-> (bar)

ArcでL-99 (P01 my-last):修正

| 18:43 | ArcでL-99 (P01 my-last):修正 - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P01 my-last):修正 - わだばLisperになる

問題を思いっきり読み違えてました…。

最後の要素を返すとのことでした。

…ということで修正!

;; L-99 (1)
;; my-last
(def lasT (lst)
  (if (atom:cdr lst)
      ((if (dotted lst) cdr car) lst)
      (lasT:cdr lst)))

;; 動作
(lasT '(foo bar baz))                   ;-> baz
(lasT '(foo . bar))                     ;-> bar

2008-02-01

ArcでL-99 (P01 my-last)

| 19:21 | ArcでL-99 (P01 my-last) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P01 my-last) - わだばLisperになる

今後、ArcでSICPに挑戦してみたり、PAIPに挑戦してみたりと色々ブログ上などで挑戦記事が増えると予想しているんですが、自分は、ArcL-99に挑戦してみることにしました!

といっても、Common Lispでも84問中64問までしか解答できていないので、まあ、途中で挫折すると思うんですが(笑)

まったり進行で、1エントリ一問って感じで挑戦して行きます。完成は、10年後位を目途に。

それとは別に、グループで掲示板が使われないのがもったいない気がするので、Arcで、SRFI-1を作ってみるというスレを立ててみました。

はてなユーザの方ならどなたでも書き込めるので、暇潰しにコードでも書いて行って下さいませ!

;; L-99 (1)
;; my-last
(def lasT (lst)
  (if (atom:cdr lst)
      lst
      (lasT:cdr lst)))

;; 伝統的なlispのlastと同じ動作
(lasT '(foo bar baz))                   ;-> (baz)
(lasT '(foo . bar))                     ;-> (foo . bar)

;; Arcのlast (SRFIっぽい動作)
(last '(foo bar baz))                   ;-> baz
(last '(foo . bar))                     ;-> Error: "Can't take cdr of bar"