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

第2回 突発性CL勉強会@Lingr 8時だョ!全員集合 告知

| 21:05 | 第2回 突発性CL勉強会@Lingr 8時だョ!全員集合 告知 - わだばLisperになる を含むブックマーク はてなブックマーク - 第2回 突発性CL勉強会@Lingr 8時だョ!全員集合 告知 - わだばLisperになる

前回新宿ルノアールでのCL勉強会も早いもので、もう1ヶ月も前のこととなってしまいました。

皆様のお蔭でSLIME勉強会は、非常に自分にとってためになるもので、SLIMEの使い方もそれ以前よりつっこんだ使い方ができるようになりました。

さて、以前、私は、CLの勉強会をどんどん増してゆこうと画策いたしておりました。

それで、物理的に集合するのは場所の確保等が大変なので、オンラインではどうかと思っておりました。

それには、まず、開催する場所が必要ということで、LingrのCommon Lisp部屋を作ってみたり、丁度、同時期にできたfreenodeの#lisp_schemeチャネルに出入りして、どんなもんかと考えつつ、ぼんやりと1ヶ月経過。

しかし、あれこれ考えても、しょうがない気がしてきたので、とりあえず開催してみることにしました。

開催というか、単なる呼掛けなのですが…。

今回のお題は、「Tutorial on Good Lisp Programming Style」というPeter Norvig氏と、Kent Pitman氏のCLのプログラミング書法論のようなものを題材にしたいと思います。

現在考えているのは以下のようなものです。

場所:Lingr: Common Lisp部屋
日時6/7 (土) 20:00から適当(途中参加/離脱/ROM歓迎)
題材「Tutorial on Good Lisp Programming Style」(日本語訳:良いLispプログラミングスタイル)
対象者CLer、Common Lispってどんなもんか知りたい人向け。後半、若干CLに依存した内容が多くなりますが、大体Lisp系言語には共通している内容と思われます。
勉強会の進行テキストを最初から参加者が眺めてゆき、質問があったり、議論になりそうなことがあったら議論してゆきます。
勉強会の目標CLに関して一つ位賢くなった気になること

心配していることは、Lingrだと発言が重なると画面にうまく反映されないことがある気がするところです。

また、なにかもっと良い題材、進行のアイディアありましたら是非教えて頂ければと思います!

ちなみに、上手く行っても行かなくても、土曜の20時にお題を変えて毎週やってみようかしらとも思っています(´▽`*)

2008-05-30

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

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

以前に作成したrangeと、rnd-selectを組み合わせる、というお題です。

あまり必要ないけれどメソッド結合。

どうやら数が大きくなってしまう場合は、このお題の方法より、シャッフルして先頭のN個を取得、という風にした方が速いみたいです…。

(defgeneric lotto-select (n range)
  (:documentation
   "P24 (*) Lotto: Draw N different random numbers from the set 1..M.
 The selected numbers shall be returned in a list."))

(defmethod lotto-select :around ((n integer) (range integer))
  (and (<= 1 n range)
       (call-next-method)))

(defmethod lotto-select ((n integer) (range integer))
  (rnd-select (coerce (vec-from-1-to range) 'list) n))

(defun vec-from-1-to (end)
  (declare ((integer 1 *) end))
  (loop :with res := (make-array end)
        :for i :from 0 :below end 
        :do (setf (svref res i) (1+ i))
        :finally (return res)))

LISP引きこもり生活 (10) SWANK環境

| 22:04 | LISP引きこもり生活 (10) SWANK環境 - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (10) SWANK環境 - わだばLisperになる

当初身の回りのものを全部CLで揃える試みを綴る予定だったのですが、どんどんひよってきてしまいました。

もっとアグレッシブに行きたいのですが、なかなか難しい…。

ここ最近で変化したことといえば、SBCLでの開発環境をちょっと変更してみていました。

どういう風に変更したかというと、SLIMEはCL側でSWANKというサーバを立てて、EMACS等のクライアントを通信するという形で開発が行われるわけなのですが、SBCLのSWANKサーバを単体の実行ファイルにしてみました。

起動時に色々設定ファイルを読ませる設定を書いたりするより、すっきりするかと思い試してみているのですが、気分的にはなんとなくすっきりするような…。

あんまり役に立ちそうもないですが、なんとなく手順を書いてみます。

1. SBCLは起動時に実行する関数が指定できるので、この機能を利用して、SWANKサーバを起動させます。

(pushnew
  (lambda () (swank:create-server :port 4005 :dont-close 'T))
   sb-ext:*init-hooks*)

sb-ext:*init-hooks*に起動時に実行する関数をリストで指定します。

2. 他、SWANK関係のいつもの設定をいろいろ

(progn
  (setq sb-impl::*default-external-format* :utf-8)
  (setq swank::*coding-system* "utf-8-unix")
  ;; ...
  )

3. 普段、イメージを作成するのと同じようにしてダンプするところで、実行ファイルにするように指定します。

(sb-ext:save-lisp-and-die "/tmp/swank-sbcl" :purify 'T :executable 'T)

これでできた、/tmp/swank-sbclを実行すれば、SWANKサーバが起動します。

2008-05-29

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

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

シーケンスの範囲切り出しには総称関数のseqが備え付けで用意されています。

今回、最後にシーケンスのクラスを調べるのに、class-ofではなくて、col-res-typeを使ってみました。

しかし、機能として一体何が違うのかは良く分かっていません…。

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

(dg slice (u|<seq> s|<int> e|<int> => <seq>))

(dm slice (u|<seq> s|<int> e|<int> => <seq>)
  (def res (packer-fab <lst>))
  (for ((x u)
        (i (from 1)))
      (when (and (<= s i) (<= i e))
        (pack-in res x)))
  (as (col-res-type u) (packer-res res)))

2008-05-28

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

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

GENSYMはマクロ登場より前に存在していたりします。

一体どういう経緯でLISPにGENSYMが導入されたのか興味があるのですが、それはさておき、番兵的にGENSYMを使ってみました。番兵にもGENSYMは便利ではあります。

そして、もう一つPROG2について考えたこと。

PROG2はPROGN系で、一番最初に導入されたもので、PROG2→PROG1 & PROGNと進化し、PROG2自体はあまり使われることはなくなってしまいました。

それで、どうして最初からNではなくて2なのかと、いうことなのですが、

(PROG2 式 (GO 〜))という使い方を想定していたのではないかと、ふと考えました。

式の部分で、状態を変更して、GOTOする、というような…。

(PROG () 式 (GO 〜))では、あくまでPROG式の内部でGOTOするだけなので、複数の式をまとめつつGOTOしたい、という場合には、ちょっと不便です。

今回本当は、PROG2を使いたかったのですが、エミュレータには、PROG2がないので、PROGで書くことになり、そんなことを考えたのでした…。

MY-PACK((A A A A B C C A A D E E E E))

;FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
;MY-PACK
;
; ((A A A A B C C A A D E E E E))
;
; END OF EVALQUOTE, VALUE IS ..
; ((A A A A) (B) (C C) (A A) (D) (E E E E E))
DEFINE((
(MY-PACK (LAMBDA (LST)
           (PROG (RES TEM L)
                 (SETQ L (REVERSE 
                          (CONS (GENSYM) LST)))
              L  (COND ((NULL L) (RETURN RES)))
                 (COND ((null tem) (SETQ TEM (CONS (CAR L) TEM)))
                       ((EQUAL (CAR L) (CAR TEM))
                        (SETQ TEM (CONS (CAR L) TEM)))
                       (T (PROG ()
                                (SETQ RES (CONS TEM RES))
                                (SETQ TEM (CONS (CAR L) () )))))
                 (SETQ L (CDR L))
                 (GO L))))
))

2008-05-27

DylanでL-99 (P08 連続して現われる要素を圧縮)

| 14:29 | DylanでL-99 (P08 連続して現われる要素を圧縮) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P08 連続して現われる要素を圧縮) - わだばLisperになる

Dylanにはdequeというコレクションクラスがあり、前後どちらの方向からも追加できるので、それを使って、蓄積して、最後に型変換してみました。

#(a:, a:, a:, a:, b:, c:, a:, a:, d:, e:, e:, e:, e:).compress
//==> #(#"a", #"b", #"c", #"a", #"d", #"e")

#[a:, a:, a:, a:, b:, c:, a:, a:, d:, e:, e:, e:, e:].compress
//==> #[#"a", #"b", #"c", #"a", #"d", #"e"]

"aaaabcaadeeee".compress
//==> "abcade"

// Code
module: l99-08

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

define method compress 
    (sequence :: <sequence>)
 => (result :: <sequence>)
  let result = make(<deque>);
  for (elt in sequence)
    if (result.empty? | elt ~= result.last)
      push-last(result, elt)
    end if
  finally
    as(select (sequence by instance?)
         <list> => <list>;
         <string> => <string>;
         <vector> => <vector>;
       end select, result)
  end for
end method compress;

2008-05-26

cl-irc

| 13:15 | cl-irc - わだばLisperになる を含むブックマーク はてなブックマーク - cl-irc - わだばLisperになる

今回のライブラリ紹介は、IRCのBOTを作成するのに便利な、cl-ircです。

パッケージ名cl-irc
ClikiCLiki: cl-irc
ASDF-INSTALL

配布されているソースの中にマニュアルのURLを貼り付けてくれるBOTのサンプルが同梱されていて便利に使えます。

このbotは、

clhs let

のような発言があると、

http://www.lispworks.com/reference/HyperSpec/Body/s_let_l.htm

のようにURLを返してくれます。

自分は、irc.freenode.netの#Lisp_Scheme部屋にもたまに連れていったりしますが、常駐させていないため、割と必要なときにいなかったりして全然役に立っていません(笑)

pfcでL-99 (P08 連続する要素を圧縮)

| 11:57 | pfcでL-99 (P08 連続する要素を圧縮) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P08 連続する要素を圧縮) - わだばLisperになる

pfcでは、可変長引数がないので、andもCLでいうeveryのようなものになっているようです。

(compress '(a a a a b c c a a d e e e e))
;==>[a b c a d e]

(define (compress lst)
  (if (null? lst)
      ()
      ((if (and [(consp (tl lst)) (= (hd lst) (hd (tl lst)))])
           (lambda (x) x)
           (cons (hd lst)))
       (compress (tl lst)))))

pfcでL-99 (P07 リストの平坦化)

| 11:57 | pfcでL-99 (P07 リストの平坦化) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P07 リストの平坦化) - わだばLisperになる

なんだか無理矢理ですが、折角なのでカリー化してみました。

(flatten '((0 (1 ((((2 (((((3 (((4)))))))) 5))))) (6 (7 8) 9))))
;=> [0 1 2 3 4 5 6 7 8 9]

(define (flatten lst)
  (if (null? lst)
      ()
      ((if (atom (hd lst))
           (cons (hd lst))
           (++ (flatten (hd lst))))
       (flatten (tl lst)))))

2008-05-24

perl-users.jp

| 18:55 | perl-users.jp - わだばLisperになる を含むブックマーク はてなブックマーク - perl-users.jp - わだばLisperになる

perl-users.jpとか、as-users.jpとか勢いがあるのを眺めると、CLでもそういうのが立ち上がらないかなー、とか思います。

perl-users.jpが立ち上げた動機を読むと、そのままCLにあてはまる気がします。

  • 最新の動向が良く分からない。
  • 初心者への情報はそれなりにウェブ上にあるけれど、中級以降への橋渡し的な情報は極端に少ない。

等々

さらに、CLの場合、

  • 謎の魔術が沢山あるようだけれど、資料が少なかったりして人知れず埋れている。

なんてのもある気がします。

実にもったいない。

しかし、やはり絶対的なユーザ数が少ないせいか、孤高の上級者と、初心者層に分かれていて中級者位の層の情報が薄いという印象もあります。

また、他の言語経験者の方が、興味を持ってCLを試してみたりしても、そういう中級の壁にぶつかって学習意欲が落ちて興味も失なうようなパターンも良く見かける気がしますし、こういうのも実にもったいない。

そしてまた、CLの魅力的なところが、その中級以上位のところの技法だったりすることが多いというのが、また残念。

なんにしろ人が増えないとどうにもならないと思うのですが、鶏と卵ですねー。

LISP引きこもり生活 (9) なんとなくDylan

| 18:19 | LISP引きこもり生活 (9) なんとなくDylan - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (9) なんとなくDylan - わだばLisperになる

最近も特にLISP引きこもり生活的には、あまり変化がありませんでした。

というか、CLで身の回りのものを固めるという作業が等閑になっているので、どうしたものかという感じです。

なんでか知りませんが、S式Dylanのマニュアルを眺めて、関数やマクロをCLに移植してみたりしています。

まとまったら、CodeReposにアップしてみたいような気もしますが、何の役にも立たないしやめておいた方が良いような気もします(^^;

LISP1.5でL-99 (P08 連続して現われる要素を圧縮)

| 18:02 | LISP1.5でL-99 (P08 連続して現われる要素を圧縮) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P08 連続して現われる要素を圧縮) - わだばLisperになる

なんとなくポインタ操作系で。

compress((a a a a b c c a a d e e e e))
; FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
;COMPRESS
;
; ((A A A A B C C A A D E E E E))
;
;
; END OF EVALQUOTE, VALUE IS ..
; (A B C A D E)

DEFINE((
(COMPRESS (LAMBDA (LST)
            (PROG (L RES TEM)
                  (SETQ L LST)
                  (SETQ RES (LIST (GENSYM)))
                  (SETQ TEM RES)
              L   (COND ((NULL L) (RETURN (CDR RES))))
                  (COND ((NOT (EQ (CAR L) (CAR TEM)))
                         (PROG ()
                               (RPLACD TEM (LIST (CAR L)))
                               (SETQ TEM (CDR TEM)))))
                  (SETQ L (CDR L))
                  (GO L))))
))

Getting Started in *LISP (10)

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

2.7 Defining *Lisp Functions and Macros

この章では、関数とマクロをどう定義してゆくかを解説

2.7.1 To Define a Function, Use defun

関数定義には、通常のCLのように、defunが使える

(defun add-mult!! (a b c)
  (*!! (+!! a b) c))

(ppp (ADD-MULT!! 1 2 3) :end 12)
;>>> 9 9 9 9 9 9 9 9 9 9 9 9 
;==> nil

(*defvar my-pvar 1)

(*set my-pvar (add-mult!! my-pvar -6 (self-address!!)))

(ppp my-pvar :end 12)
0 -5 -10 -15 -20 -25 -30 -35 -40 -45 -50 -55 NIL

という風に割とCLのまま書ける。

*Lispには、*defunというものも用意されているが、特殊な状況を除き使用する必要はなく、殆どdefunでまかなえるとのこと。

2.7.2 To Define a Macro, Use defmacro

マクロの定義もお馴染みのdefmacroで定義。

(defmacro *two-incf (pvar)
  `(*set ,pvar (+!! ,pvar 2)))

(*TWO-INCF my-pvar)

(ppp my-pvar :end 12)
;>>> 2 -3 -8 -13 -18 -23 -28 -33 -38 -43 -48 -53
;==> NIL

というようにCLそのままな感じ。

2.8 Compiling *Lisp Code

コンパイルについてもCLと同様で、関数単位、ファイル単位で可能。

(compile 'add-mult!!)

(compile-file "starlisp-code.lisp")
(load "starlisp-code")
  • A Note about Declarations

コンパイラへの宣言については、*Lispでは、関数の引数と返り値の型を完全に明確に宣言しないと、完全にコンパイルされないということに注意せよ、とのこと。

詳細は、5章で解説。

完全な宣言の例

(defun add-mult!! (a b c)
  (declare (type (pvar (signed-byte 32)) a b c))
  (*!! (+!! a b) c))

型宣言しなければ、コンパイルされないわけではなく、パフォーマンス等で機能を十全に発揮させるには、型宣言が必要になることに注意せよ、とのこと。

2.9 Summary: How *Lisp and Common Lisp Are Similar

CLと*Lispの共通点についてのまとめ

  • pvarは、CLのアレイのようなもの
  • pvarのプリティプリントにはpppがある
  • 並列動作する関数は、pvarに対して動作する並列版CL関数ともいうべきもの
  • defunや、defmacro等CLと同じものが使える
  • コンパイラは、*Lisp用に拡張されたCLのコンパイラのようなもの

次回、3章から再開です。

GOOでL-99 (P17 指定した位置でリストを分割)

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

GOOには、splitが既に存在しているのですが、Perlのsplitと同じようなもののため、my-splitとして作成。

また、GOOには多値がないのですが、その代わりとしては、タプルを使うことになっていようです。

色々な型に対応するということで、分割した結果をタプルで返すことにしてみました。

seqは、CLのsubseqに相当し、seq*は、(seq x (len seq))と同様の働きをするものです。

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

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

(my-split #[a b c d e f g h i k] 3)
;==> #(#[a b c] #[d e f g h i k])

(my-split "abcdefghik" 3)
;==> #("abc" "defghik")

(dg my-split (u|<seq> pos|<int> => <seq>))

(dm my-split (u|<seq> pos|<int> => <seq>)
  (tup (sub u 0 pos) (sub* u pos)))

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

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

P20で定義したREMOVE-ATを使えとありますが、L-99の元になったP-99では、REMOVE-ATが結果と残りのリストの両方を結果として返すものなので、このお題で使えるということのようです。

ということで、REMOVE-ATを修正して使ってみました。

しかし無駄に長いなあ…。

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

(rnd-select #(a b c d e f g h) 3)
;=> #(D E G)

(rnd-select "abcdefgh" 3)
;=> "gbc"

(defgeneric RND-SELECT (sequence number)
  (:documentation
"P23 (**) Extract a given number of randomly selected elements from a list.
    The selected items shall be returned in a list." ))

(defmethod RND-SELECT :around ((sequence sequence) (count integer))
  (let ((len (length sequence)))
    (cond ((zerop len) sequence)
          ((or (> 1 count) (< len count)) 
           (MAKE-EMPTY-SEQUENCE sequence))
          ('T (call-next-method sequence count)))))

(defmethod RND-SELECT ((sequence sequence) (count integer))
  (loop :with seq := sequence
        :with res := ()
        :for len := (length seq) :then (1- len) 
        :for i :from count :downto 1 :when (> 1 count) :do (loop-finish)
        :do (multiple-value-bind (s item) (REMOVE-AT seq (1+ (random len)))
              (push item res)
              (setq seq s))
        :finally (return (coerce res (class-of sequence))))))

(defgeneric REMOVE-AT (sequence position)
  (:documentation "P20 (*) Remove the K'th element from a list."))

(defmethod REMOVE-AT ((sequence sequence) (position integer))
  (values (concatenate (class-of sequence)
                       (subseq sequence 0 (1- position)) 
                       (subseq sequence position))
          (elt sequence (1- position))))

(defgeneric make-empty-sequence (obj))
(defmethod make-empty-sequence ((obj list)) 
  () )
(defmethod make-empty-sequence ((obj vector))
  (make-array 0))
(defmethod make-empty-sequence ((obj string))  
  (make-string 0))

2008-05-21

無名総称関数

| 12:43 | 無名総称関数 - わだばLisperになる を含むブックマーク はてなブックマーク - 無名総称関数 - わだばLisperになる

CLtL2には、generic-functionという無名総称関数を作成するものがあったりしたのですが、ANSI CLでは姿を消してしまいました。

何となく面白そうなので、このgeneric-functionをどうやったら再現できるのかと以前から探っていたのですが、make-instanceで、総称関数が作れることが分かったので、add-methodと組み合わせてgeneric-functionの代わりにしてみました。

まあ、見るからに役に立ちそうもありませんが…。

(let* ((gf (make-instance 'standard-generic-function))
       (gf (add-method 
            gf
            (make-instance 'standard-method 
                           :function (lambda (x y) 
                                       (declare (ignore y))
                                       (format nil "~A => string." (car x)))
                           :lambda-list '(x)
                           :specializers (list (find-class 'string)))))
       (gf (add-method 
            gf
            (make-instance 'standard-method 
                           :function (lambda (x y) 
                                       (declare (ignore y))
                                       (format nil "~A => symbol." (car x)))
                           :lambda-list '(x)
                           :specializers (list (find-class 'symbol))))))
  (mapcar gf '("foo" "bar" x y z)))

;=> ("foo => string." "bar => string." "X => symbol." "Y => symbol." "Z => symbol.")

DylanでL-99 (P07 リストの平坦化)

| 11:48 | DylanでL-99 (P07 リストの平坦化) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P07 リストの平坦化) - わだばLisperになる

最近の?Dylanはドットで連結できるのですが、これだと見た目は、かなりLispっぽくなくなる気がします。

let seq = #(1, #(2, 3, #(4, 5, #(#(#(6, 7)),8))), 9);
format-out("%= => %=\n",seq, seq.flatten);
//=> #(1, #(2, 3, #(4, 5, #(#(#(6, 7)), 8))), 9)
//  => #(1, 2, 3, 4, 5, 6, 7, 8, 9)

// Code
module: l99-07

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

define method flatten
    (sequence :: <list>)
 => (result :: <list>)
  case
    sequence.empty? 
      => sequence;
    instance?(sequence.head, <list>)
      => concatenate(sequence.head.flatten,
                     sequence.tail.flatten);
    otherwise
      => pair(sequence.head,
              sequence.tail.flatten);
  end
end method flatten;

2008-05-19

構造体と総称関数の組み合わせ

| 07:16 | 構造体と総称関数の組み合わせ - わだばLisperになる を含むブックマーク はてなブックマーク - 構造体と総称関数の組み合わせ - わだばLisperになる

bit別冊 Common Lisp オブジェクトシステム - CLOSとその周辺を読んでいて、defmethodとdefstructを組み合わせる例が紹介されていたので試してみます。

なんだか関係のないところに力が入ってしまいましたが、CLでは、クラスと型が融合していて、defstructで作った型も引数特定子として扱えるということが本題です…。

(setq ガガンボ (make-双翅目))
(setq オニヤンマ (make-蜻蛉目))
(setq なんらかの虫 (make-昆虫))

(昆虫の足の数 ガガンボ)
;=> 6

(昆虫の足の数 オニヤンマ)
;=> 6

(双翅目の足の数 ガガンボ)
;=> 6

(双翅目の翅の数 ガガンボ)
;=> 2

(虫博士 なんらかの虫)
;=> 昆虫は足が6本だけど、そんな虫知らない。

(虫博士 ガガンボ)
;=>昆虫は足が6本、双翅目は、翅が2枚なんだ。

(虫博士 オニヤンマ)
;=>昆虫は足が6本、蜻蛉目は、翅が4枚なんだ。

(虫博士 'foo)
;=> それは虫じゃないね。

;; 定義
(defstruct (昆虫 (:conc-name "昆虫の"))
  (足の数 6))

(defstruct (双翅目 (:include 昆虫) (:conc-name "双翅目の"))
  (翅の数 2))

(defstruct (蜻蛉目 (:include 昆虫) (:conc-name "蜻蛉目の"))
  (翅の数 4))

(defmethod 虫博士 ((虫 双翅目))
  (format t "昆虫は足が6本、~Aは、翅が~D枚なんだ。~%" 
	  (type-of 虫) (双翅目の翅の数 虫)))

(defmethod 虫博士 ((虫 蜻蛉目))
  (format t "昆虫は足が6本、~Aは、翅が~D枚なんだ。~%" 
	  (type-of 虫) (蜻蛉目の翅の数 虫)))

(defmethod 虫博士 ((虫 昆虫))
  (format t "昆虫は足が6本だけど、そんな虫知らない。~%"))

(defmethod 虫博士 (虫)
  (format t "それは虫じゃないね。~%"))

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

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

今回は、8.3から再開です。

8.3 真偽値を扱う手続き

8.3.1 等価述語以外の述語

SchemeもCLも大体同じです。というか、SchemeとCLが分岐する前から定番になっていたところなので、似ているのも当然といえば、当然。

  • 型を判定するもの
schemeCL
pair?consp
null?null
boolean?(typep x 'boolean)
symbol?symbolp
number?numberp
char?characterp
string?stringp
  • 性質を判定
odd?oddp
even?evenp
zero?zerop
char-alphabetic?alpha-char-p

CL(というか伝統的LISPでは)最後に付くpは、predicateの略で、述語には、-pを付ける伝統があります。最初期の述語には、atom、null等、語尾にpはついてませんが、60年代初頭のLISP 1.5から、既に-pという述語が定着しているようなので、LISP1から、1.5の間になんらかの出来事があったんでしょう。

8.3.2 否定

notの使い方。

CLのnotは、NILが多様な意味を持つために、nullと全く同じ働きをするのですが、両者は意味的に使いわけられています。

8.3.3 述語を取る手続き

これは、SRFIとCLでは名前の付け方にちょっと違いがあり

SRFICL
anysome
everyevery

のようになっています。

(some #'oddp '(2 4 8 1))
;=> T

(every #'oddp '(1 3 5))
;=> T
  • every-pred

every-predはないので作成

(defun every-pred (&rest fns)
  (lambda (&rest arg)
    (every (lambda (f) (every f arg))
           fns)))

(defun some-pred (&rest fns)
  (lambda (&rest arg)
    (some (lambda (f) (some f arg))
          fns)))

なんとなくanyじゃなくて、CLっぽくsome-predにしました。

every-pred応用例

(setf (symbol-function 'positive-integer?) 
      (every-pred #'integerp #'plusp))

(positive-integer? 4)
;=> T

(positive-integer? -3)
;=> NIL

(positive-integer? 2.4)
;=> NIL

(setf (symbol-function 'nonnegative?) (complement #'minusp))

(nonnegative? 2)
;=> T

(nonnegative? 0)
;=> T

(nonnegative? -2)
;=> NIL

[練習問題]

上で書いてしまってました…。

8.4 条件判断

CLとschemeと違うところは、

(if nil 'foo)
;=> NIL

となり、未定義にはならない所。

  • cond

CLには、Schemeのようにcondで=>は使えません。

setqが代入時の結果の値を返すことと、代入を組み合わせて

(let ((lst '(55 77 99)))
  (let (=>)
    (cond ((setq => (find-if #'oddp lst)) (* => 2))
          (:else 0))))
;=> 110

というのもたまに見かけます。(主に80年代位のコードで)

condでは、もともと=>が使えたりはしないので、いつから=>が使えるようになったのかと調べてみると、どうも、1978年のThe Revised Report on SCHEME(AIM-452)位からのようです。

P17に定義があるのですが、CLのマクロに翻訳してみました。

(import 'kmrcl:with-gensyms)

(defmacro test (pred fn alt)
  (with-gensyms (p f a)
    `(let ((,p ,pred) 
           (,f (lambda () ,fn))
           (,a (lambda () ,alt)))
       (if ,p 
           (funcall (funcall ,f) ,p)
           (funcall ,a)))))

(defmacro rs-cond (&rest clauses)
  (with-gensyms (v r)
    (cond ((null clauses) ''nil)
          ((null (cdar clauses))
           `(let ((,v ,(caar clauses))
                  (,r (lambda ()
                        (rs-cond ,@(cdr clauses)))))
              (if ,v ,v ,r)))
          ((eq (cadar clauses) '=>)
           `(test ,(caar clauses) ,(caddar clauses)
                  (rs-cond ,@(cdr clauses))))
          (t `(if ,(caar clauses)
                  (progn ,@(cdar clauses))
                  (rs-cond ,@(cdr clauses)))))))

(test 33333 #'list nil)
;=> (33333)

再帰的に展開され、ifに全部変換されます。

testマクロが肝のようですが、良くこんなの考えつくなーと感心します。

SRFI-61の拡張は、主に述語部が多値を返して、それを結果として受け取りたいような場合に使うようです。

CLでも、

(cond ((values 1 2))) ;=> 1

のようにcondの述部は、多値を返しません。

SRFI-61に相当するものもないので、欲しい場合は作ることになります。

自分も前に汚ないのを作ってみたことがあります。

8.4.2 CASE

caseはelse節のキーワードが、elseか、otherwiseかの違い位で同じです。

SRFI-87のような拡張はありません。

(case 1
  (0 'zero)
  (otherwise 'one))
;=> ONE

これも前に作ってみました。

8.4.3 andとor

andとorについても同じです。

8.4.4 whenとunless

これまた、CLと、Schemeとで違いはありません。

8.4.5 and-let*

and-let*はCLにないので、作るほかありません。

(defmacro and-let* ((&rest binds) &body body)
  (reduce (lambda (c res)
            `(let ((,(car c) ,(cadr c)))
               (and ,(car c) ,res)))
          binds
          :initial-value `(progn ,@body)
          :from-end 'T))

(and-let* ((a :a)
           (b :b)
           (c :c))
  (print (list a b c)))

;=> (:A :B :C) 

CL界隈には、and-let*的なものはないので便利なときには便利かもしれません。

次回9章から再開です。

2008-05-18

cCLan

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

久し振りにcclanのページをみたら、表記がcCLanとなっていた。(前から?)

もしや、と思って確認してみたら、CTANや、CPANに範をとったプロジェクトとのこと。

http://sourceforge.net/projects/cclan/

知らなかった…。

しかし、CLRFI(SRFIの真似)といい、cCLanといい、最初から共有を目指したプロジェクトがあまり芳しい成果をあげていないように見えるのはなぜなんだろう…。

ArcでL-99 (P59 左右で高さのバランスのとれた二分木)

| 05:09 | ArcでL-99 (P59 左右で高さのバランスのとれた二分木) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P59 左右で高さのバランスのとれた二分木) - わだばLisperになる

ここでいう左右で高さのバランスのとれた二分木とは、左右の木た高さの差が±1までの二分木とのこと。

本来バックトラックで解くところですが、全通り生成しております。

そして、hbal-treeで条件を満した木を選り分けているのですが、最初から条件を満した木を生成してしまっているため、意味のないことになっております…。

;(each x (firstn 5 (hbal-tree 3)) (prn x))
;>>>
;(x (x (x nil nil) (x nil nil)) (x (x nil nil) (x nil nil)))
;(x (x (x nil nil) nil) (x (x nil nil) (x nil nil)))
;(x (x nil (x nil nil)) (x (x nil nil) (x nil nil)))
;(x (x (x nil nil) (x nil nil)) (x (x nil nil) nil))
;(x (x (x nil nil) nil) (x (x nil nil) nil))

(def hbal-tree (h)
  (keep hbal-tree-p gen-tree-h.h))

(def gen-tree-h (h)
  (case h
    0 '(())
    1 '((x () ()))
    (with (h-1 (gen-tree-h (- h 1))
           h-2 (gen-tree-h (- h 2)))
      (map (fn (tree) `(x ,@tree))
           `(,@(comb2 h-1 h-1)
             ,@(comb2 h-1 h-2)
             ,@(comb2 h-2 h-1))))))

(def hbal-tree-p (tree)
  (let (_ left right) tree
    (>= 1 (abs (- tree-height.left 
                  tree-height.right))))

(def tree-height (tree)
  (let (_ left right) tree
    (if tree
        (+ 1 (max tree-height.left
                  tree-height.right))
        0)))

CLOSでL-99 (P22 指定した範囲の数列のリスト)

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

色々な型に対応しつつ、コードの重複を避けるように考えているのですが、いまいちCLOS流というのが掴めていません…。

今回は、フロントに総称関数を置いて共通部分は、関数に括り出すという感じにしてみました。

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

;(mapc #'princ (range #\あ #\お))
;=> あぃいぅうぇえぉお

(defgeneric RANGE (start end)
  (:documentation
   "P22 (*) Create a list containing all integers within a given range."))

(defmethod RANGE ((start integer) (end integer))
  (RANGE1 start end #'values))

(defmethod RANGE ((start character) (end character))
  (let ((start (char-code start))
        (end (char-code end)))
    (RANGE1 start end #'code-char)))

(defun RANGE1 (start end fn)
  (declare (integer start end))
  (if (< start end)
      (loop :for i :from start :to end :collect (funcall fn i))
      (loop :for i :from start :downto end :collect (funcall fn i))))

2008-05-16

GOOでL-99 (P16 周期Nで要素を間引く)

| 00:24 | GOOでL-99 (P16 周期Nで要素を間引く) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P16 周期Nで要素を間引く) - わだばLisperになる

GOOでは、CLで有名なRichard C. Water氏のSERIESを手本にした遅延評価のシリーズが組み込みで用意されています。

rangeや、from等、scan-rangeっぽいものがあるのですが、汎用的なforループの中で使えるというところが便利です。

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

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

;(drop #[a b c d e f g h i j k] 3)
;=> #[a b d e g h j k]

;(drop "abcdefghijk" 3)
;=> "abdeghjk"

(dg drop (u|<seq> n|<int> => <seq>))

(dm drop (u|<seq> n|<int> => <seq>)
  (def res (packer-fab <lst>))
  (for ((x u)
        (cnt (from 1)))
    (unless (zero? (rem cnt n))
      (pack-in res x)))
  (as (class-of u) (packed res)))

2008-05-15

Getting Started in *LISP (9)

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

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

2.4 The Size and Shape of Pvars

重要な違いはあるものの、これまでの内容からもPVARとCLのアレイは似たようなものと考えることができることが分かった。

共通点としては、

  • 多数の要素を持つ
  • 要素の形(次元等)と、要素数を指定する。
  • インデックスを指定して読み書きする。
  • 関数に引数として与えられた場合は、値を渡すのではなく、参照を渡す。

等がある。

アレイのサイズの指定のように、PVARの配列サイズはどのようにして決めるか、ということになるが、cold-boot時に指定する方法と後の章で説明する方法がある。

2.4.1 Processor Grids and Configurations

とりあえず、*cold-bootでの初期化の方法の解説。

*cold-bootを呼ぶとPVARは初期化されるが、引数なしで、*cold-bootが呼ばれた場合、搭載されているCPUの数となり、2次元のグリッドで、CPUの個数に応じて適宜調整される。

8K個の場合は、64 x 128 に配置される(8192)

シミュレータの場合は、デフォルト値8 x 4(16)

2.4.2 *cold-boot

*cold-bootの様子

(*cold-boot)

Thinking Machines Starlisp Simulator.  Version 20.0

1 ;シミュレータなので、1個ということか?…。
(16 16)
Configuration Variables

グリッドについては、色々と変数がある。

CPUの数

*number-of-processors-limit*
;=> 256

グリッドの形状

*current-cm-configuration*
;=> (16 16)

2.4.3 Processor Grids and Pvar Shapes

*current-cm-configuration*が8192の場合、(!! 24)を実行すると、64 * 128のグリッド全部に24が格納された状態になっている。

2.4.4 Pvars of Different Shapes and Sizes-VP Sets

プログラム中で、pvarのグリッドの形を変更したくなった場合はどうするか。

そういう場合のためにvirtual processor sets (VP sets)という概念が用意されているが、とりあえず、初歩的な説明を続けるとのこと。

2.5 Calling Parallel Functions

並列関数?を色々と使ってみた例

(ppp (+!! (self-address!!) 3) :end 20)
;>>> 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 

(ppp (*!! (self-address!!) pi) :end 4)
;>>> 0.0d0 3.141592653589793d0 6.283185307179586d0 9.42477796076938d0

(ppp (float!! (1+!! (self-address!!))) :end 12)
;>>> 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0

(ppp (sin!! (*!! (self-address!!) (/!! pi 4))) :end 4)
;>>> 0.0d0 0.7071067811865475d0 1.0d0 0.7071067811865476d0 

(let ((i #C(0.0 1.0)))
  (ppp (exp!! (*!! 2 pi i)) :end 3))
>>> #C(1.0d0 -2.4492127076447545d-16) #C(1.0d0 -2.4492127076447545d-16) #C(1.0d0 -2.4492127076447545d-16)
;; シミュレータだと、数値がちょっと違う…

(ppp (random!! 20) :end 20)
;>>> 3 5 13 4 7 18 15 13 18 19 10 0 8 11 12 4 11 14 9 9
*Lispの関数名について

CLの関数に対応するものが、*Lispの並列版として用意されているが、

  • 大抵末尾に'!!'が付くか、先頭に'*'が付く
  • 動作は殆ど同じだが、並列に動作する

のが特徴。

'!!'(読み:バンバン)が末尾に付いたものは、並列に動作し、いつもPVARを返す。

'*'(読み:スター)が付いたものは、並列に動作するが、基本的にPVARは返さないもの。

という方針で名付けられているとのこと。

2.6 Printing Pvars in Many Ways

PVARのプリティプリント関数である、pppの解説。

オプションを色々解説。

一番簡単なオプションは、:endで、指定した場所まで、表示する。

(*defvar my-pvar 5)

(ppp my-pvar :end 20)
;>>> 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 

:start は開始位置を指定する。

(ppp (self-address!!) :start 20 :end 30)
;>>> 20 21 22 23 24 25 26 27 28 29

:per-line で一行あたりの表示個数を指定できる。

(ppp (self-address!!) :end 30 :per-line 12)
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 

:grid を使うことで現在のグリッドを表示することもできる。

(ppp (self-address!!) :mode :grid :end '(4 4))

;>>>
;     DIMENSION 0 (X)  ----->
;
;0 16 32 48 
;1 17 33 49 
;2 18 34 50 
;3 19 35 51 

:format を付けてさらにみやすく整形。フォーマット指示子は、formatと同じものが使えるらしい。

(ppp (self-address!!) :mode :grid :end '(4 4) :format "~2D ")
;>>>
;     DIMENSION 0 (X)  ----->
;
; 0 16 32 48 
; 1 17 33 49 
; 2 18 34 50 
; 3 19 35 51 

(ppp (self-address!!) :end 32 :per-line 8 :format "~2D " :title "Send addresses")

:title で、グリッドのタイトルを指定可能
;>>>
;Send addresses:
; 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 

LISP1.5でL-99 (P07 リストの平坦化)

| 02:16 | LISP1.5でL-99 (P07 リストの平坦化) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P07 リストの平坦化) - わだばLisperになる

LISTPがないので自作。NILは空リストということで削除する方針で作成。

; FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
;FLATTEN
;
;((0 (1 ((((2 (((((3 (((4)))))))) 5))))) (6 (7 8) 9)))
;
;END OF EVALQUOTE, VALUE IS ..
;(0 1 2 3 4 5 6 7 8 9)

DEFINE((
(LISTP (LAMBDA (LST)
         (OR (NULL LST) (NOT (ATOM LST)))))

(FLATTEN (LAMBDA (LST)
           (COND ((NULL LST) () )
                 ((LISTP (CAR LST)) 
                  (APPEND (FLATTEN (CAR LST)) (FLATTEN (CDR LST))))
                 (T (CONS (CAR LST) (FLATTEN (CDR LST)))))))
))

2008-05-13

pfcでL-99 (P06 リストが回文的かを判定)

| 23:41 | pfcでL-99 (P06 リストが回文的かを判定) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P06 リストが回文的かを判定) - わだばLisperになる

ううむ。もっと先の問題にならないとpfcならでは、というところが見えてこないのかもしれない…。

前回定義したrevを使用。

(palindrome? '(x a m a x))
;=> #t

(def (palindrome? lst)
  (= (rev lst) lst))

DylanでL-99 (P06 シーケンスが回文的かを判定)

| 22:00 | DylanでL-99 (P06 シーケンスが回文的かを判定) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P06 シーケンスが回文的かを判定) - わだばLisperになる

比較の=は、CLでいうequalみたいなもので、==だとeqになる様子。

doは、CLのmapc、Schemeのfor-each。

let lst = #(x:, a:, m:, a:, x:);
let str = "xamax";
let vec = #[x:, a:, m:, a:, x:, a:];

do (method (x) format-out("%= => %=\n", x, x.palindrome?) end,
    list(lst, str, vec));
//>>>
// #(#"x", #"a", #"m", #"a", #"x") => #t
// "xamax" => #t
// #[#"x", #"a", #"m", #"a", #"x", #"a"] => #f

// code
module: l99-06

define generic palindrome? 
    (sequence :: <sequence>)
 => (result :: <boolean>);

define method palindrome?
    (sequence :: <sequence>)
 => (result :: <boolean>)
  (sequence = sequence.reverse)
end method palindrome?;

2008-05-12

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

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

今回は8章「真偽値と条件判断」から再開です。

8.1 述語と等価述語

CLでも「述語」とはいうような気がしますが、等価述語というのかは良く分かりません(^^;

8.2 等価性

8.2.1 内容が同じ

CLでequal?に相当するのは、equalで

(equal (list 1 2 3) (list 1 2 3))
;=> T

(equal (list 1 2 3) (cons 1 (list 2 (+ 1 1 1))))
;=> T

両者の型と値が同じならば等しいというところも同じです。

8.2.2 入れ物が同じ
(eq (cons 1 2) (cons 1 2))
;=> NIL

(let ((x (cons 1 2)))
  (eq x x))
;=> T

eq?に相当するものは、eqで、入れ物、つまり、LISPのオブジェクトとして等しい場合に真。ポインタの比較に喩えれるのも一緒で、確実に比較できるのは、シンボルのみというのも一緒。

また、シンボルについての説明で、同じ名前のシンボルは、同じ実体を指すというもの同じ。

しかし、CLの場合パッケージがあり、正確な名前はパッケージ名付きであることに注意する必要があります。

カレントパッケージとパッケージ名が同一の場合は、省略できるので、xはcl-user::xやfoo::x等、パッケージごとに複数存在可能です。

この場合、名前が違えば、全部違うオブジェクトかといえばそうでもなく、シンボルが外部パッケージから取り込まれている(importした)場合は、違うパッケージ名でもeqになることがあります。取り込んで来たオブジェクトなので、同一なオブジェクトであるのは自明ではありますが、ややこしいところでもあります。

8.2.3 数値として同じ
(eql 1 1)
;=> T

(eql 1.0 1.0)
;=> T

(eql 1 1.0)
;=> NIL

eqlは、型が同じで、値が同じならTというもの同じ

値を比較したい場合には、=を使うというのも同じ。

(= 1 1.0)
;=> T

文字の比較

(char= #\a #\a)
;=> T

(char= #\a #\A)
;=> NIL

(eql #\a #\a)
;=> T

HyperSpecには、

eql and equal compare characters in the same way that char= does.

とあるので、eqlとequalはchar=の機能としても使えるようです。

  • 文字列
(string= "abc" "abc")
;=> T

(string= "abc" "ABC")
;=> NIL
  • 大文字小文字の違いを無視したい場合
;; char-ci=?、string-ci=?
(char-equal #\a #\A)
;=> T

;; string=ci?
(string-equal "abc" "ABC")
;=> T

名前が若干違っています。-equalという風に冗長に書かなければいけない場合には、大文字小文字を区別しない、という命名規則です。

8.2.4 その他の等価性

isomorphic?はCLにはないので、探してみたのですが、みつからないので、Gaucheのを適当に移植してみました。といっても禄にテストしていないので、ちゃんと機能するのか不明です(^^;

(let ((p (cons 1 2)))
  (isomorphic? (list p p) (list p '(1 2))))
;=> nil

(let ((p (cons 1 2)))
  (isomorphic? (list p p) (list p p)))
;=> t

(let ((p (make-array 100 :fill-pointer 1 :adjustable 'T)))
  (vector-push 1 p)
  (isomorphic? p (vector 0 (+ 0 1))))
;=> t

;; 
(defun ISOMORPHIC? (a b &rest args)
  (let ((ctx (if (consp args)
                 (if (hash-table-p (car args))
                     (car args)
                     (error "hash table required, but got ~S." (car args)))
                 (make-hash-table))))
    (ISO? a b ctx)))

(defun ISO? (a b ctx)
  (let (win tem)
    (cond ((or (characterp a) (numberp a))
           (eql a b))
          ((null a) (null b))
          ((progn (setf (values tem win) (gethash a ctx))
                  win) ;node has been appeared
           (eq tem b))
          (:else
           (setf (gethash a ctx) b)
           (typecase a
             (cons (and (consp b)
                        (iso? (car a) (car b) ctx)
                        (iso? (cdr a) (cdr b) ctx)))
             (string (and (stringp b) (string= a b)))
             (keyword (eql a b))
             (symbol (eq a b))
             (vector (VECTOR-ISO? a b ctx))
             (otherwise (OBJECT-ISOMORPHIC? a b ctx))))))))

(progn
  (declaim (inline vector->list))
  (defun VECTOR->LIST (vec) (coerce vec 'list)))

(defun VECTOR-ISO? (a b ctx)
  (and (vectorp b)
       (do ((la (vector->list a) (cdr la))
            (lb (vector->list b) (cdr lb)))
           ((endp la) (endp lb))
         (cond ((endp lb) (return nil))
               ((ISO? (car la) (car lb) ctx))
               (:else (return nil))))))

(defmethod OBJECT-ISOMORPHIC? (a b context)
  (equal a b))
8.2.5 等価述語をとる手続き

(member-if 等価述語 リスト)のようなことを示しているのだと思いますが、これはCLでも同じです。

ということで次回は、8.3章から再開です。

2008-05-11

ArcでL-99 (P58 線対称な二分木を探す)

| 18:02 | ArcでL-99 (P58 線対称な二分木を探す) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P58 線対称な二分木を探す) - わだばLisperになる

前に作成したcbal-treeとsymmetric?を組み合わせて作成。

また、57ノードの時に線対称な二分木は幾つかという問いもあり。

57ノードの場合、cbal-treeで作成する木が多くてコンスが多くなりすぎるためか、CLだと、2、3秒のところが、Arcだと、解答に11分位かかってしまいます。

keepは、CLでは、remove-if-notに相当します。

(each tr sym-cbal-trees.5
  prn.tr)
;>>> (x (x (x nil nil) nil) (x nil (x nil nil)))
;>>> (x (x nil (x nil nil)) (x (x nil nil) nil))

;(len:sym-cbal-trees 57)
;=> 256

(def sym-cbal-trees (n)
  (keep symmetric? cbal-tree.n))

2008-05-10

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

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

call-next-methodすると色々できそうなんだけど、乱発すると何だか良く分からないことになるなあ…。

(insert-at 'alfa '(a b c d) 2)
;=> (A ALFA C D)

(insert-at 'alfa #(a b c d) 2)
;=> #(A ALFA C D)

(insert-at 'alfa "abcd" 2)
;=> "aALFAcd"

(defgeneric INSERT-AT (item sequence position)
  (:documentation "P21 (*) Insert an element at a given position into a list."))

(defmethod INSERT-AT (item (sequence array) (position integer))
  (call-next-method (if (stringp sequence) (to-string item) (vector item))
                    sequence
                    position))

(defmethod INSERT-AT (item (sequence list) (position integer))
  (call-next-method (list item) sequence position))

(defmethod INSERT-AT (item (sequence sequence) (position integer))
  (concatenate (class-of sequence)
               (subseq sequence 0 (1- position))
               item
               (subseq sequence position)))

(defgeneric TO-STRING (obj)
  (:documentation "coerce to string."))

(defmethod TO-STRING ((obj symbol)) (string obj))

(defmethod TO-STRING ((obj string)) obj)

(defmethod TO-STRING (obj) (write-to-string obj))

GOOでL-99 (P15 要素を任意回数複製する)

| 18:03 | GOOでL-99 (P15 要素を任意回数複製する) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P15 要素を任意回数複製する) - わだばLisperになる

色々な型に対応しようと思うと、どうも再帰というよりは、forのようなループを多用してしまうような…。

LISPで再帰を多用するのは、扱うデータが主にリストだからという面もあるのでしょうか。

;(repli '(a b c c d) 3)
;=> (a a a b b b c c c c c c d d d)

;(repli #(a b c c d) 3)
;=> #(a a a b b b c c c c c c d d d)

;(repli #[a b c c d] 3)
;=> #[a a a b b b c c c c c c d d d]

;(repli "abccd" 3)
;=> aabbccccdd

(dg repli (u|<seq> times|<int> => <seq>))

(dm repli (u|<seq> times|<int> => <seq>)
  (def res (packer-fab <lst>))
  (for ((x u))
    (for ((i (range 1 <= times)))
      (pack-in res x)))
  (as (class-of u) (packed res)))

Getting Started in *LISP (8)

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

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

2.2 Data Parallelism-A Different Value for Each Processor

これまでの内容で、全プロセッサに同じ値を割り付けることは分かった。

しかし、*Lispのデータパラレリズム言語としての肝は、各々のプロセッサが同一のオペレーションをするというところにあり、大抵各プロセッサで違う内容のデータを処理することになるので、それの解説。

各プロセッサに別々の値を割り付けるものには、既出のものでは、random!!がある。

同様のものとしては、self-address!!がある。

プロセッサには一意の番号が定数として割り付けられているので、その番号を取得するもの。

(ppp (self-address!!) :end 20)
;>>> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

アドレスを指定して値を取得するには、2.1.4で説明されたprefが使える。

2.3 Pvar Data Types

Pvarで利用可能な色々なデータ型を解説。

まず、*Lispへスカラー値が渡った場合は、pvarとみなされるとのこと。

つまり、5と書いても、(!! 5)と見做されるということ。

ブール値

t!!、nil!!等があり、また述語もあり。

(ppp (evenp!! (self-address!!)) :end 10)
;=> T NIL T NIL T NIL T NIL T NIL 
unsigned-byte、 signed-byte
(+!! 3 5)

(ppp (-!! (self-address!!) 800) :end 10)
;>>> -800 -799 -798 -797 -796 -795 -794 -793 -792 -791 
defined-float
(float!! 34)

(ppp (/!! (self-address!!) 4) :end 10)
;>>> 0.0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 2.0 2.25 
complex
(complex!! 3 1)

(ppp (complex!! (self-address!!) 1) :end 5)
;>>> #C(0 1) #C(1 1) #C(2 1) #C(3 1) #C(4 1) 
character
(!! #\C)
(int-char!! 23)

(ppp (!! #\C) :end 10)
;>>> #\C #\C #\C #\C #\C #\C #\C #\C #\C #\C 

;; int-char!!がエミュレータにはない。
;; code-char!!になっている様子。

(ppp (code-char!! 23) :end 10)
;>>> #\^w #\^w #\^w #\^w #\^w #\^w #\^w #\^w #\^w #\^w 
array
(ppp (make-array!! '(2 8) :element-type 'single-float :initial-element pi) :end 1)
;>>> #2A((3.141592653589793d0 3.141592653589793d0 3.141592653589793d0
;         3.141592653589793d0 3.141592653589793d0 3.141592653589793d0
;         3.141592653589793d0 3.141592653589793d0)
;        (3.141592653589793d0 3.141592653589793d0 3.141592653589793d0
;         3.141592653589793d0 3.141592653589793d0 3.141592653589793d0
;         3.141592653589793d0 3.141592653589793d0)) 
structure
(*defstruct particle
  (x-pos 0 :type (unsigned-byte 32))
  (y-pos 0 :type (unsigned-byte 32)))

(*let ((sts (make-particle!! :x-pos 20 :y-pos 61)))
  (ppp (particle-x-pos!! sts) :end 10))
;>>> 20 20 20 20 20 20 20 20 20 20 
2.3.1 Other Pvar Types

その他に扱えるものとして、front-endがあり、参照を格納できるらしい。

(ppp (front-end!! 3) :end 10)

(ppp (front-end!! '(foo bar baz)) :end 5)
;>>> (FOO BAR BAZ) (FOO BAR BAZ) (FOO BAR BAZ) (FOO BAR BAZ) (FOO BAR BAZ) 
(ppp (!! '(foo bar baz)) :end 10)
;!!! Cannot put values of type CONS into a pvar
;となり、pvarにはコンスは格納できない。

上記のように、コンス(リスト)はPVARに格納できないので、扱うとすれば、ポインタ参照経由でということになるのだろうか。

まだ、それぞれのプロセッサごとに異なる参照を格納する解説はなし。

ということで、色々な型のデータをpvarとして格納できることが分かった。

また、PVAR変数の作成は、

(*defvar my-pvar 5)

のようにするが、型を指定したpvarの作り方については、5章で解説するとのこと。

2008-05-08

LISP引きこもり生活 (7) とにかく全部CLでそろえるという夢

| 20:31 | LISP引きこもり生活 (7) とにかく全部CLでそろえるという夢 - わだばLisperになる を含むブックマーク はてなブックマーク - LISP引きこもり生活 (7) とにかく全部CLでそろえるという夢 - わだばLisperになる

今週は特にLISP引きこもり生活的には、あまり変化の無い週でした。

quekさんのエントリ

を読んで、自分もClimacsで、SLIMEで、SKKで、EMACSいらずな方向に向かいたい!と思いましたが、climacsが上手く起動せず早速挫折しました。

説明が後になりましたが、Climacsとは、CLで実装されたエディタでEMACSみたいなエディタです。

うーん、32bit Ubuntuの時は動いてたんだけどなあ。64bit Ubuntuだと何か問題あるんだろうか…。そんなことないよなあ。

Closureという、CLで実装されたウェブブラウザもあるのですが、これも動かない、32bitの時は動いてたのに、何が違うんだろうなあ。

いずれにせよ、MCCLIMがちゃんと設定できてないんだろうなあ。

CADRでFLAVORS

| 16:10 | CADRでFLAVORS - わだばLisperになる を含むブックマーク はてなブックマーク - CADRでFLAVORS - わだばLisperになる

CADRエミュレータで何かしようと思いつつも前回からまた一ヶ月経ってしまいました。

最近CLOSのような、LISP上でのOOPになんとなく興味があるのですが、CADRには、CLOS以前のflavorsというOOPの枠組がありました。

よくRubyなどで、mixinとかいう用語が使われますが、この用語は、確かflavorsに由来していたと思います。

アイスクリームのトッピングに由来した用語なのですが、flavorsという名前自体、そのトッピングのことを指してるみたいです。

とりあえず、何から始めたら良いか良く分からないので、ポール・グレアム著のANSI CLのCLOSの章をなぞってみることにしました。

とりあえず、構造体とクラスを比較してみる、という内容なのですが、defstructの細かいところが、CLとLisp Machine Lispで違っていたりして、それもまた乙です。

とりあえず、CLの構造体だとこんな感じです。

;; CL
(defstruct rectangle
  height
  width)

(defstruct circle
  radius)

(defun area (x)
  (cond ((rectangle-p x)
         (* (rectangle-height x) (rectangle-width x)))
        ((circle-p x)
         (* pi (expt (circle-radius x) 2)))))

(let ((r (make-rectangle)))
  (setf (rectangle-height r) 2
        (rectangle-width r) 3)
  (area r))
;=> 6

自動で、rectangle-widthのようなアクセサを作ってくれて、rectangle-pのような述語も作成してくれます。

次にLisp Machine Lispでの構造体でのバージョンです。

;; Lisp Machine Lisp
(defstruct (rectangle :named)
  rectangle-height
  rectangle-width)

;; アクセッサはフルネームじゃないと駄目
;; namedにしないと、
;; (typep obj 'rectangle)とか効かないので、
;; namedで作らないとrectangle-pのようなものが作れないらしい。

(defstruct (circle :named)
  circle-radius)

;; piは定数ではないので定義
(defconst pi 3.141592653589793238)

;; 自動で、述語を作ってくれないので自作
(defmacro make-pred (name)
  `(defun ,(intern (string-append (string name) "-P")) (obj)
     (typep obj ',name)))

(make-pred rectangle)
(make-pred circle)

(defun area (x)
  (cond ((rectangle-p x)
         (* (rectangle-height x) (rectangle-width x)))
        ((circle-p x)
         (* pi (expt (circle-radius x) 2)))))

(let ((r (make-rectangle)))
  (setf (rectangle-height r) 2)
  (setf (rectangle-width r) 3)
  (area r))
;=> 6

そして、Lisp Machine LispのFlavorsで

;; Lisp Machine Lisp
(defflavor rectangle (height width) ()     
  :gettable-instance-variables
  :settable-instance-variables)

(defflavor circle (radius) ()
  :gettable-instance-variables
  :settable-instance-variables)

(defmethod (rectangle :area) ()
  (* (funcall-self ':height) (funcall-self ':width)))

(defmethod (circle :area) ()
  (* pi (expt (funcall-self ':radius) 2)))

(let ((r (make-instance 'rectangle)))
  (<<-- r
        (':set-height 2)
        (':set-width 3)
        (':area)))
;=> 6

ざっとした説明ですが、まず、キーワードに一々クオートが付いています。

これは、Lisp Machine Lispでは、CLのようにキーワードパッケージがあって、そのシンボルの評価結果が自分自身となるようなものではなく、ただの目印的なものなので、クオートが必要になります。

次に、defflavorですが、これは、CLOSのdefclassに相当します。

書式は、

(defflavor クラス(フレイバー)名 (スーパークラス)
  ...)

のような感じで、

:gettable-instance-variables

:settable-instance-variables

:inittable-instance

は値を取得/設定/初期化できるようにする指示で、デフォルト値も設定できます。

とりあえず、フレーバーを作ったら、

(setq r (make-instance 'rectangle))

(<- r ':set-width 3)

のようにして、値を設定できたりします。

rというオブジェクトに:set-widthと3というメッセージを送るのですが、

(funcall r ':set-width 3)

でも良かったりするみたいです。

ちなみに<-は後に、sendという名前になったようです。

set-〜というところは、
settable-instance-variablesをすると自動的に:set-〜で値を設定することができるようになります。

それで、defmethodですが、

(defmethod (フレーバー キー?) (引数)
  ...)

となり、CLのdefmethodとはちょっと違っています。

呼び出し方ですが、

(<- r ':area)

のようになるようです。

funcall-selfですが、defmethod内では自身を指すselfという変数が使えるので、これは、(<- self ...mesg)と同じことのようです。

CLOSのように(area r)とは書けないみたいでなんですが、初期のFlavorsの試行錯誤の後、「関数呼び出しの形とメソッド呼び出しの形を一緒にしたら良いんじゃないか?」というアイディアが現われて形が統合されたらしいです。

<<--は、(progn (<- ..) (<- ..))に展開されるマクロで上の例は、

(let ((r (make-instance 'rectangle)))
  (<- r ':set-height 2)
  (<- r ':set-width 3)
  (<- r ':area))

と同じです。

まとめ

という感じで、自分も全然分かっていないので、解説らしい解説にもなっていないのですが、色々いじって遊んでみようと思っています!

2008-05-07

Lingr CL-JP 今日投稿されたネタと質問

| 19:15 | Lingr CL-JP 今日投稿されたネタと質問 - わだばLisperになる を含むブックマーク はてなブックマーク - Lingr CL-JP 今日投稿されたネタと質問 - わだばLisperになる

5/6

(1)CLにWeak Objectsが足りないってどういうこと?

(2)CL入門のためのサンプルコードどう書く「ファイルの存在を確認する。」

;; ゴルフ的に
(format t"~A は、存在しま~:[せん~;す~]~%"#1="a.txt"(probe-file #1#))

(3)CLで、Perlの$foo =~ s/foo/bar/;的なもの、どう書く

(let ((message "I play tennis."))
  (setf (ppcre:scan "tennis" message) "baseball")
  message)
;=> "I play baseball."

解答例

LISP1.5でL-99 (P06 回文的かを調べる)

| 18:41 | LISP1.5でL-99 (P06 回文的かを調べる) - わだばLisperになる を含むブックマーク はてなブックマーク - LISP1.5でL-99 (P06 回文的かを調べる) - わだばLisperになる

LISP 1.5にも、EQと、EQUALがあって、いまいち使い分けが分かっていませんが、この文脈では、EQUALを使うべきなようです。

PALINDROMEP((X A M A X))

; END OF EVALQUOTE, VALUE IS ..
; *TRUE*


DEFINE((
(PALINDROMEP (LAMBDA (LST)
               (EQUAL (REVERSE LST) LST)))
))

2008-05-06

DylanでL-99 (P05 シーケンスを反転させる )

| 20:23 | DylanでL-99 (P05 シーケンスを反転させる ) - わだばLisperになる を含むブックマーク はてなブックマーク - DylanでL-99 (P05 シーケンスを反転させる ) - わだばLisperになる

うーん、入力のクラスと同じクラスで出力させたいのだけれど、object-classでは上手く行かないので、selectで振り分け。

絶対変な書き方だと思うんだけれども…。

forは、CLのdoを強化したようなもので、loopの機能も取り入れていますが、基本的に構文はdoに良く似ています。

#(foo:, bar:, baz:).rev
//=> #(baz:, bar:, foo:)

#[foo:, bar:, baz:].rev
//=> #[baz:, bar:, foo:]

"foobarbaz".rev
//=> "zabraboof"

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

define method rev
    (sequence :: <sequence>)
 => (result :: <sequence>)
  let result = make(<deque>);
  for (elt in sequence)
    push(result, elt);
  finally 
    as(select (sequence by instance?)
         <list> => <list>;
         <string> => <string>;
         <vector> => <vector>;
       end select, result)
  end for
end method rev;

pfcでL-99 (P05 リストを反転させる)

| 12:49 | pfcでL-99 (P05 リストを反転させる) - わだばLisperになる を含むブックマーク はてなブックマーク - pfcでL-99 (P05 リストを反転させる) - わだばLisperになる

pfcでは、appendを++とも書けるようです。listがないので、[]で囲んでいます。

(def (rev lst)
  (if (null lst)
      ()
      (++ (rev (tl lst)) [(hd lst)])))

Lingr CL-JP 今日投稿されたネタと質問

| 09:08 | Lingr CL-JP 今日投稿されたネタと質問 - わだばLisperになる を含むブックマーク はてなブックマーク - Lingr CL-JP 今日投稿されたネタと質問 - わだばLisperになる

5/5

質問への回答

loop の collect で vector を返すってなさそう

話題

iterateは色々拡張できるらしい。

seriesも良いんじゃないか?→seriesの解説が欲しい。

SBCLで、ADVICE

関数にフックをかけたり外したりできるという機能の紹介。

via:viridian-project.de

総称関数の:before、:after、:aroundや、EMACSのdefadviceのようなもの。

総称関数以外の関数や、CLパッケージの関数もいじれるというところがメリットでは?

その他:

総称関数から特定のメソッド外すのも結構途方に暮れる

→結局fmakunboundしてしまうことが多いw

総称関数で+を書き換えると引数なしだと単位元という部分が崩れる。

引数なしの、総称関数の存在意義は?

MacOSX用のCL環境オールインワンパッケージの新バージョンリリース

No page found

2008-05-05

CLの開発環境/ライブラリ等の設定

| 18:01 | CLの開発環境/ライブラリ等の設定 - わだばLisperになる を含むブックマーク はてなブックマーク - CLの開発環境/ライブラリ等の設定 - わだばLisperになる

quekさんのletter: [Common Lisp] SBCL 環境 ライブラリ ~/.sbclrc

を読んで、確かにCLerの人々は、処理系をどういう設定で使っているのか、という情報は少ないなぁと思ったので、参考になるかどうかは疑問ですが、自分もどういう感じで使っているかを晒してみることにしました!

処理系

大した理由はないのですが、できるだけ沢山の処理系をできるだけ沢山のOSで動かすというのを目標にしています。

現在、SBCL、Clozure CL、Allegro CL、LispWorks、ECL、ABCL、CMUCL、MCLをMacOSX、Linuxの組み合わせで使っていて、MCL以外は、大体全部同じライブラリ環境で揃えています。ちなみにFFI系を利用するものが揃えられないのは、諦めています(^^;

ライブラリの設定

ライブラリ環境を揃えるのには、以前もこのブログで書いたasdf-addonsを利用しています。

asdf-addonsを利用すると、複数の処理系でライブラリを揃えるのが非常に楽なのでお勧めです。

他に似たものとしては、asdf-binary-locationsがあるようです。

処理系ごとの初期化ファイル

自分は、処理系ごとにSWANKが起動できて頻繁に利用するパッケージを全部読み込んだイメージをダンプしています。

イメージの作成にはmake-core.lispのような処理系で共通のファイルを作成して、各処理系が共通のライブラリを読み込んでダンプしています。

そして、作成したイメージを使ってSWANKを起動し、SLIMEで接続して利用しています。

各処理系毎の初期化ファイルは殆ど利用していませんが、Allegroの試用版だと処理系起動時にコアファイルとロードさせるファイルの両方を指定できないようなので、

.clinit.clに

;; .clinit.cl
;; -*- lisp -*-
;(princ "Hello! init file")

(and (find-package :swank)
     (load (merge-pathnames"etc/slime.lisp" (user-homedir-pathname))))

のようにして、SWANKを起動させるのに初期化ファイルを例外的に利用している位です。

まとめ

という感じで、自分は、環境をいちいちダンプしているのですが、ダンプするのもバッチ処理的なファイルを作成しておけば良いので割と頻繁にダンプしています。

イメージをダンプできないもの(LispWorksの評価版など)もあるのですが、この場合は、イメージ作成用のバッチファイルが、環境をロードするためのファイルになるようにしています。

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

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

7.7章から再開です。

7.7 省略可能引数とキーワード引数

省略可能引数とキーワード引数は、Gaucheの拡張で便利なのでCLから輸入されたとのことです。

CLでは、Gaucheと違って前回の内容でも書いたラムダリストパラメータで色々指定することになります。

可変長引数をリストとして受け取ってlet-optionals*で自前で分解する、という方法ではなく、

(defun my-make-list (num &optional args))

という風に指定することになります。

どうしても、let-optionalsが使いたいんだ!!という場合は、マクロを書くことになると思います…。

(defmacro let-optionals* ((&rest args) (&rest vars-and-inits) &body body)
  (let ((vars-and-inits (copy-list vars-and-inits)))
    `(let* ,(map-into vars-and-inits 
                      (lambda (val vi) `(,(car vi) ,val))
                      args
                      vars-and-inits)
       ,@body)))

(let-optionals* (1 2) ((a 100) (b 200) (c 300))
  (list a b c))
;=> (1 2 300)

等々。

キーワードに関しても、同じように、

(defun my-make-list (num &key (init-elt nil)))

のように指定します。

また、キーワードかどうか判定するには、keywordpを使います。

(keywordp :foo)

キーワード引数のメリットとしてはGauche本の解説の通りですが、CLの場合、:fooというのは、keywordパッケージに登録されたシンボルということになり、keyword:fooの省略形であるというところが違います。また、キーワードはnilや、tのように自己を評価した結果は自分自身になります。

ちなみに、何がなんでも、let-keywordが使いたいんだ!!という場合は、これまたマクロを書くことになると思います…。

(defmacro let-keyword (args (&rest keys-and-inits) &body body)
  (let* ((g (gensym))
         (vars-and-inits (mapcar (lambda (x) 
                                   `(,(intern (string (car x)))
                                      (or (getf ,g ,(car x)) ,(cadr x))))
                                 keys-and-inits)))
    `(let ((,g ,args))
       (let ,vars-and-inits
         ,@body))))

;; other-argsに対応するの忘れてました…。

どっちにしろ、普通に引数で指定した方が便利なので、CLを学ぼうという方は、そちらで書いて下さい(^^;

7.8 部分適用

ここでは、cutの説明がありますが、CLにはcutに相当するスタンダードなものはないと思います。

しかし、色々な試みはあって、リーダーマクロ

(mapcar #'(lambda (x) (* 2 x))
        '(1 2 3))

(mapcar #L(* 2 _) '(1 2 3))

のように書けるようにしたりしている人は多いようです。

参考:no title

また、cutマクロを作るのもそんなに難しくないと思うので自作されるのも良いかと思います。

自分は、CL版のdefine-syntaxを利用してcutを移植してみたことがありました。

define-syntaxでcut:Common Lispであんまり衛生的ではないdefine-syntax - わだばLisperになる - cadr group

7.9 多値

多値の役割についても、SchemeとCLで違いはないと思います。

7.9.1 多値を受けとる

CLでSchemeのreceiveに相当するものは、 multiple-value-bindになるかと思います。

;(defun min&max (&rest args)
;  (values (apply #'min args) (apply #'max args)))

(multiple-value-bind (min-val max-val)
    (min&max 3 1 2)
  (list min-val max-val))

;=> (1 3)

let-valuesとlet-values*に相当する標準的なものはありませんが、そういうユーティリティは沢山あると思います。

自分は、metabang-bindを良く使います。

このパッケージのbindは、destructuring-bindと、multiple-value-bindを一緒にした上に構造体もバインドできるというものです。

let-valuesだと

(bind (((:values min-val max-val) (min&max 3 1 2)))
  (list min-val max-val))

のように書けます。

value-refに相当するものとしては、nth-valueがあり

(nth-value 1 (min&max 3 1 2))
;=> 3

という風になります。

7.9.2 多値を返す

多値を返すのには、Schemeと同じくvaluesを使います。

(values 1 2 3 4)
;=> 1,2,3,4

ということで、次回は8章からです。

2008-05-03

gensymのリスト

| 11:58 | gensymのリスト - わだばLisperになる を含むブックマーク はてなブックマーク - gensymのリスト - わだばLisperになる

当初、気の効いたイディオムを探索するという趣旨でしたが、見付けようと思ってもなかなか見付からないので、適当にイディオムを探してエントリを書くことにしました(^^;

さて、CLのマクロを書いていると、変数のリストと同じ長さのgensymのリストが欲しくなることがあると思います。

'(a b c)
;=>
(#:g000001 #:g000002 #:g000003)

そこに焦点を当てて人間模様を観察してみたいと思います。

とりあえず、

(defparameter *lst* '(foo bar baz))

としておきます。

(1)Paul Graham氏

(mapcar (lambda (x) (gensym)) *lst*)

;; コンパイラに警告されるので、ignoreする場合。
(mapcar (lambda (x) 
	  (declare (ignore x))
	  (gensym))
	*lst*)

アンチloopの人なので、mapcarで。

declareあり、45文字。なしは、31文字

(2)適当に変種。declareが長いので、valuesで2値目を捨てるぞ、という場合。

(mapcar (lambda (x) (values (gensym) x))
	*lst*)

でも、分かりにくい。42文字

(3)やっぱりloopが定番だろう

(loop :for g :in *lst* :collect (gensym))

loopがぴったり嵌まる状況な気がする。36文字。

アンチloopじゃない人は、普通にこちらかも。

(4)dolistはどうか。

(let (res)
  (dolist (g *lst* res)
    (push (gensym) res)))

見たことない。49文字

(4)Brian Mastenbrook氏(common-idiomsより)

わざわざ、fconstantlyというconstantlyに似た関数を定義して使う。

(mapcar (fconstantly #'gensym) *lst*)

fconstantlyに汎用性はあるんだろうか。基本的にこのシチュエーション専用な気が…。

fconstantlyをインポートすれば、32文字。しなければ、46文字。

まとめ

以上、色々(といっても基本的に3バージョン)纏めてみましたが、最短は、31文字で、declareなしのPG方式。警告回避の場合32文字でfconstantly

しかし、やっぱりloopが定番なのではないでしょうか。

もっと良い方法があったら教えて下さい!

ArcでL-99 (P57 二分探索木の作成)

| 10:51 | ArcでL-99 (P57 二分探索木の作成) - わだばLisperになる を含むブックマーク はてなブックマーク - ArcでL-99 (P57 二分探索木の作成) - わだばLisperになる

久々のArc。今回のお題は、数値のリストを二分探索木的に配置しましょう、というもの。

また、その結果を前回作成した、symmetric?で確認してみよう、とのことです。

(construct '(3 2 5 7 1))
;=> (3 (2 (1 nil nil) nil) (5 nil (7 nil nil)))

;; symmetric?で確認
(symmetric? (construct '(5 3 18 1 4 12 21)))
;=> t

(symmetric? (construct '(3 2 5 7 1)))
;=> nil

(def add-leaf (leaf tree)
  (with ((root left right) tree
         node `(,leaf () () ))
    (if (<= leaf root)
        (if no.left
            `(,root ,node ,right)
            `(,root ,(add-leaf leaf left) ,right))
        (if no.right
            `(,root ,left ,node)
            `(,root ,left ,(add-leaf leaf right))))))

(def construct (lst)
  (reduce (fn (lst leaf) (add-leaf leaf lst))
          (let (head . tail) lst
            (cons `(,head () () ) tail))))

COMMON LISP JP 今日投稿されたネタと質問

| 06:22 | COMMON LISP JP 今日投稿されたネタと質問 - わだばLisperになる を含むブックマーク はてなブックマーク - COMMON LISP JP 今日投稿されたネタと質問 - わだばLisperになる

毎日LingrのCL部屋に投稿されたネタを纏めてみることにしました。

といっても、殆ど私が投稿しているだけであり、激しく自作自演な状況です(笑)

良かったら質問とかしてみて下さい。

質問の場合、タイトル+内容という感じで書くと後で探しやすく、回答もしやすいかなと思っています。

話題:

5/1

  1. SLIMEの最近の変更 - debugオプションの追加
  2. stumpwm
  3. SLIMEとanything.el

5/2

  1. cl-win32oleがCodeReposへ
  2. ひげぽんさん、Lisp/SchemeのIRCチャンネル物色中
  3. Worse is better

質問

  1. LOOPマクロの質問

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

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

なんとなく、「面倒版」ではベクタを基本にして、他は型変換させるように書いてみましたが、色々な型に対応する関数を書くときはやっぱりリストを基本にするんでしょうか。

型変換するにもコストが掛かるだろうし、型ごとに個別に記述するのも面倒だし、落し所の判断が難しいなあ(;´Д`)

ベンチマーク取ったりして判断するのかな?

(remove-at #(1 2 3 4 5 6) 1)
;=> #(2 3 4 5 6)
(remove-at '(1 2 3 4 5 6) 1)
;=> (2 3 4 5 6)
(remove-at "123456" 1)
;=> "23456"

(defgeneric REMOVE-AT (sequence position)
  (:documentation "P20 (*) Remove the K'th element from a list."))

;; 単純版
(defmethod REMOVE-AT ((sequence sequence) (position integer))
  (concatenate (class-of sequence)
               (subseq sequence 0 (1- position)) 
               (subseq sequence position)))

;; 面倒版
(defmethod REMOVE-AT ((sequence vector) (position integer))
  (let ((len (length sequence)))
    (if (not (<= 1 position len))
        (copy-seq sequence)
        (loop :with res := (if (stringp sequence)
                               (make-string (1- len))
                               (make-array (1- len))) 
              :and ridx := 0 
              :for x :across sequence
              :for idx :from 1 :to len
              :unless (= position idx) 
                  :do (setf (aref res ridx) x) 
                      (incf ridx)
              :finally (return res)))))

(defmethod REMOVE-AT ((sequence sequence) (position integer))
  (coerce (remove-at (coerce sequence 'vector) position)
          (class-of sequence)))

2008-05-01

Getting Started in *LISP (7)

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

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

Chapter 2 The *Lisp Language

この章では、

  1. *Lispの並列型データの基本としてのpvar
  2. データパラレリズム
  3. *Lispでの並列版関数が元のCLのどの関数に相当するか
  4. 並列版関数の定義とコンパイル

についての詳細を解説するとのこと。

2.1 Creating and Using Parallel Variables

*Lispの並列処理で基本となるデータ型は、pvar(Parallel Variable)

このpvarは各々Connection Machineの1プロセッサに対応していて、number、character、array、structure型が扱える。

2.1.1 Creating a Pvar - !!

*Lispで一番単純な並列操作は、!!(バンバンと読むそうな…)で、スカラー値を引数に取り、pvarを返します。

(!! 24)

;=> #<PVAR CONSTANT-24 :GENERAL  *DEFAULT-VP-SET* (8 4)>

これは、24という値を全部のプロセッサに送るもの。

使われるのは一時的な格納場所ということで、!!では、各プロセッサに値を保持することはできない。

2.1.2 Permanent Pvars - *defvar

値を再度設定するまで永続化したい場合は、*defvarを使う。

(*defvar my-pvar 5)

全プロセッサのmy-pvarに5を代入。

*Lispでは、最初から2つの並列版のpvarが設定されていて、並列版t!!と、nil!!がある。CLでは、tとnilに相当。

2.1.3 Local Pvars - *let

*defvarは、大域的で、ローカルにpvarを設定したい場合には、(なんか微妙な表現だな(^^; ) *letが使える

(*let ((two!! 2)
       (three!! 3.0))
  (+!! two!! three!! my-pvar))

これで、全プロセッサで2 + 3.0を計算する。演算子も+!!という並列版を使う必要あり。

2.1.4 Reading, Writing, and Printing Pvar values

プロセッサは沢山あるけれど(Connection Machine(CM-2)の場合65536個)特定のプロセッサの値を見たい場合は、prefが使える。"processor reference"の略でarefの類推から来てるらしい。

(pref my-pvar 12)
;=> 5

これは、12番プロセッサの値を取得する。上で、*defvarで全部5に設定したので、5が返ってくる。

値を代入したい場合は、*setfを使う。

(*setf (pref my-pvar 12) 42)
;=> 42
(pref my-pvar 12)
;=> 42

値をプリティプリントしたい場合は、pppか、pretty-print-pvarを使う。

(ppp my-pvar :end 20)
;>>> 5 5 5 5 5 5 5 5 5 5 5 5 42 5 5 5 5 5 5 5 

12番目が、42になっているのが確認できる。

*setfの他には、set*が使える。これは、setqの並列版なのか、setの並列版なのかどっちなのだろう。

(*set my-pvar 9)

(ppp my-pvar :end 20)
;>>> 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 NIL

(*defvar copy-pvar)
; (ちなみに、初期化しないと#:ILLEGAL-VALUE-21421で埋まっている模様)
(ppp COPY-PVAR :end 1)

(*set copy-pvar my-pvar)
(ppp copy-pvar)
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 NIL

代入できた。

一括で扱うのには、*系、プロセッサで並列に実行させたり、その値は、!!系の名前になっているんだろうか。

GOOでL-99 (P14 各要素を2倍する)

| 15:02 | GOOでL-99 (P14 各要素を2倍する) - わだばLisperになる を含むブックマーク はてなブックマーク - GOOでL-99 (P14 各要素を2倍する) - わだばLisperになる

GOOでもSLIMEが使えるようなので、今回使ってみました。

CVS版SLIMEのcontribの中にある、swank-goo.gooを使うのですが、CL用の設定と競合してしまうようで、slime-setupで読み込むものは、軒並み読み込まないで利用する必要があるようです。

手元で確認できたGOOのバージョンは、155でgooとg2cとありますが、g2cの方を利用。

使い勝手としては、goo-shellだとエラーの度にREPLに移動して、リスタート候補を選択するのが面倒だったのですが、SLIMEだと簡単になった、位でしょうか。飛躍的に便利になる訳ではないようです(^^;

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

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

;(dupli #[a b c c d])
;=> #[a a b b c c c c d d]

;(dupli "abccd")
;=> aabbccccdd

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

(dm dupli (u|<seq> => <seq>)
  (def res (packer-fab <lst>))
  (for ((x u))
    (pack-in res x)
    (pack-in res x))
  (as (class-of u) (packed res)))

オンライン勉強会への道 (1)

| 12:37 | オンライン勉強会への道 (1) - わだばLisperになる を含むブックマーク はてなブックマーク - オンライン勉強会への道 (1) - わだばLisperになる

オフラインで勉強会ができればベストなのですが、物理的に集合しなければならないので、オンラインでの勉強会ができないかと模索しています。

別にCL初心者の私が模索する必要は全くないのですが、どうして勉強会などを呼び掛けてみているのかといえば、勉強会を開くことによってオンラインで入手できる日本語のCLの情報が今よりずっと増えるのではないか?と勝手に考えているからなのです。

私は、

  1. CLer(Lisp面白そう!という人)が増える
  2. CLerが情報発信するようになる
  3. CLの情報が増える
  4. 自分も調べもの等で幸せになれる。

という流れを狙っていて、「自分も調べもので幸せになれる」には、まず自分が調べものをして分かったことを書きまくるのが一番の近道だと思ってこのブログを割と毎日のように更新しています。(単にブログ依存なだけかもしれません(笑) )

ネタ的にも全然CLのことじゃなかったりしますし、CLerの役に立つ内容かと訊かれれば、「いえ、全然(^^;」と答えるしかないですが、とりあえず書いています。

そういうスタンスなのですが、同じように

  1. テーマを絞って情報が薄そうなところを興味のある人が集まって勉強会を行なう
  2. きっと有用な情報が得られて、勉強会のログを参加者が書く
  3. 有用なCLの情報が増える
  4. 自分も調べもの等で幸せになれる

という御利益があるんじゃないかと思うのです。

そういう流れなので、IRCで勉強会を考えてみたりしているのですが、私は、IRCを殆ど使ったことがなく、また、IRCはログ収集を別にしないといけないらしいので、とりあえず、僭越ながらLingrでCLの部屋を作ってみました!

とりあえず、勉強会というよりLISP関係のネタ収集/投稿ができる場という感じを想定しています。

多分、人は全然集まらないので、日によってお題を決めて、ネタを投稿して行くというネタ駆動で回して行くのも良いかなと思っています。

ということで、良かったら是非利用してみて下さい!

初心者質問も大歓迎で、入門的な質問が集まったらFAQにしてどっかに纏めたいと考えています。