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 |

2011-04-26

プロジェクトの最小セットを考えてみる (2)

| 20:51 | プロジェクトの最小セットを考えてみる (2) - わだばLisperになる を含むブックマーク はてなブックマーク - プロジェクトの最小セットを考えてみる (2) - わだばLisperになる

前回のエントリーの考えを検証するためQuickProjectのような雛形生成プログラムを用意してみました。

といっても、QuickProjectを読み込み自分好みに関数を上書き定義しただけのものです。

自分の好みで変更したところとしては、

  • #:fooより:fooの方が好きなのでキーワードを使った
  • githubでは、org-modeのファイルも扱えるようなのでREADME.txtをREADME.orgにした
  • ファイルの構成を前回のエントリーのようにした

位です。

試しにqpj1のプロジェクト自体をqpj1自身で作ってみました。

単純なことですが、雛形があるとやっぱり便利です。

一歩進めて、githubへの公開までREPL上でできると最高なので誰か作って欲しいところ。

とりあえず、これでしばらくゴミコードを書き散らかしたいと思います。

2011-04-25

プロジェクトの最小セットを考えてみる

| 18:41 | プロジェクトの最小セットを考えてみる - わだばLisperになる を含むブックマーク はてなブックマーク - プロジェクトの最小セットを考えてみる - わだばLisperになる

Rubyの人達のように気軽にgithubにプロジェクトをぽんぽん作るにはどうしたら良いだろうか、と思ってプロジェクトの最小セットあれこれ考えてみています。

今回書いてみるのは、かなりイレギュラーながらテストを含めて3つのファイルで記述してみる試みです。

ファイル構成

  • package.lisp
    • パッケージの記述
  • project-name.asd
    • ファイルやライブラリの依存関係など記述
  • project-name.lisp
    • コード本体

中身

prnというArcのprnを真似たもの1つだけがあるプロジェクトを例にしてみます。

  • package.lisp
;;;; package.lisp

(defpackage #:prn
  (:export :prn))

(defpackage #:prn-internal
  (:use #:prn
        #:cl
        #:fiveam))
  1. prnがメインパッケージ。妙なところは、CLパッケージをuseしないところ。メインパッケージは、パッケージからエクスポートするシンボルを管理するだけです。
  2. prn-internalがコード上での本体。テストライブラリ/その他もこのパッケージにuseしてしまいます。メインパッケージのシンボルをuseすると便利っぽいのでuseしてみます。
  3. prn.asd
;;;; prn.asd

(in-package :asdf)

(defsystem #:prn
  :serial t
  :depends-on (#:fiveam)
  :components ((:file "package")
               (:file "prn")))

(defmethod perform ((o test-op) (c (eql (find-system :prn))))
  ;; テストするには本体をロードする必要がある
  (load-system :prn)
  ;; テスト実行
  (or (flet ((_ (pkg sym)
         (intern (symbol-name sym) (find-package pkg))))
        ;;
        (let ((result (funcall (_ :fiveam :run) (_ :prn :prn))))
          (funcall (_ :fiveam :explain!) result)
          (funcall (_ :fiveam :results-status) result)))
      ;; Tで抜けなかったらエラー
      (error "test-op failed") ))

テストが付属してくるプロジェクトでは、大抵メインと、メイン-testのような2つの構成になっていますが、一つにしてみます。

システムにasdf:performメソッドを用意してやることによって、 (asdf:test-system :prn)できるようにします。

中身がごちゃごちゃしてしまっているのは、asdファイルが本体よりも先に読まれるため読み込み時点ではまだ存在しないシンボルを踏まないようにするためです。

この部分でテストの実行関数を呼ぶことになります (上記では、 (fiveam:run) を呼んでいます)

  • prn.lisp
;;;; prn.lisp

(in-package #:prn-internal)

(def-suite prn)

(in-suite prn)

(defun prn (&rest args)
  (format *standard-output*
          "~{~A~}~%"
          args))

(test prn
  (is (string= "ABCD
"
               (with-output-to-string (*standard-output*)
                 (prn 'a 'b 'c 'd))))
  (is (string= "abcd
"
               (with-output-to-string (*standard-output*)
                 (prn "a" "b" "c" "d")))))

本体のコード。prn-internalパッケージ内で定義します。

テストもずらずら並べて書いてしまいます。上の例では、fiveamを利用。

注意する点は今回の場合メインパッケージをuseしているので、prn:prnであるということ。

ロード

(asdf:load-system :prn)

でロードでき、

(asdf:test-system :prn)

でテストが走ります。

(asdf:test-system :prn :force T)

で色々コンパイルしなおしながらテストを実行

色々とイレギュラーで、テストとコードがごっちゃになる、シンボルの扱いが若干複雑、等々問題はある気がしますが、書捨てコードの雛形にはこれでも良いかなあと考えています。

何かアイデア/問題ありましたら是非教えてください!

2011-04-23

C.I.CLを眺める(13) MEMQ

| 22:29 | C.I.CLを眺める(13) MEMQ - わだばLisperになる を含むブックマーク はてなブックマーク - C.I.CLを眺める(13) MEMQ - わだばLisperになる

今回は、C.I.CLのlist.lispから MEMQ です。

MEMQ は CLより前のMacLISPには標準で存在していたものですが、CLでは、比較関数(:TEST)が別に取れるようになったため、比較関数ごとに特化したものはなくなりMEMBER一つとなりました。

それでも、EQに特化したMEMQは割と愛用されているようで、大抵の処理系では内部や拡張パッケージにはMEMQが存在しているようです。

ということで定義は

(DEFUN MEMQ (ITEM LIST)
  "
RETURN:   (MEMBER ITEM LIST :TEST (FUNCTION EQ))
"
  (MEMBER ITEM LIST :TEST (FUNCTION EQ)))

となっています。たまに#'ではなくFUNCTIONと書くのが、Bourguignon氏の特徴ですが何故なのかは謎。

動作は、

(import 'com.informatimago.common-lisp.list:memq)

(memq :bar '(foo :bar baz))
;=> (:BAR BAZ)

自作する際に注意したいのが最適化の指定ですが、C.I.CLでも

(DECLAIM (INLINE PLIST-PUT PLIST-GET PLIST-REMOVE MEMQ))

のようにインライン展開の指定があります。これがないと

  • memq (inline指定なし)
(dotimes (i 100000000)
  (memq :bar '(foo :bar baz)))
;⇒ NIL
----------
Evaluation took:
  0.961 seconds of real time
  0.960000 seconds of total run time (0.960000 user, 0.000000 system)
  99.90% CPU
  2,300,195,133 processor cycles
  0 bytes consed

Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz
  • member
(dotimes (i 100000000)
  (member :bar '(foo :bar baz)))
;⇒ NIL
----------
Evaluation took:
  0.044 seconds of real time
  0.040000 seconds of total run time (0.040000 user, 0.000000 system)
  90.91% CPU
  104,561,685 processor cycles
  0 bytes consed

Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz

のように大幅に逆効果になってしまうこともあるようなので注意が必要かもしれません。

2011-04-21

C.I.CLを眺める(12) PLIST-REMOVE

| 18:10 | C.I.CLを眺める(12) PLIST-REMOVE - わだばLisperになる を含むブックマーク はてなブックマーク - C.I.CLを眺める(12) PLIST-REMOVE - わだばLisperになる

今回は、C.I.CLのlist.lispから PLIST-REMOVE です。

PLISTから指定したキーを持つ(最初の)組を削除する、というものです。

REMFがあるじゃないかと思いましたが、REMFは削除の結果に応じて真偽値を返すので、その辺りの使い勝手を調整したもののようです。

(DEFUN PLIST-REMOVE (PLIST PROP)
  "
DO:      (REMF PLIST PROP)
RETURN:  The modified PLIST.
"
  (REMF PLIST PROP)
  PLIST)
  • REMF
(let ((plist (list :foo 1 :bar 2 :baz 3)))
  (remf plist :foo))
;=> T
  • PLIST-REMOVE
(let ((plist (list :foo 1 :bar 2 :baz 3)))
  (plist-remove plist :foo))
;=> (:BAR 2 :BAZ 3)

PLIST-REMOVEの返り値の方が使い道はありそうです。

2011-04-19

C.I.CLを眺める(11) PLIST-GET

| 21:04 | C.I.CLを眺める(11) PLIST-GET - わだばLisperになる を含むブックマーク はてなブックマーク - C.I.CLを眺める(11) PLIST-GET - わだばLisperになる

今回は、C.I.CLのlist.lispから PLIST-GET です。

前回はPUTでしたが今回はGET。

PLISTから情報を取り出す関数には標準でGETFがありますが、定義もPLIST-PUTとの整合性のために作られたエイリアスという感じです。

(DEFUN PLIST-GET (PLIST PROP)
  "
 Extract a value from a property list.
 PLIST is a property list, which is a list of the form
 (PROP1 VALUE1 PROP2 VALUE2...).  This function returns the value
 corresponding to the given PROP, or nil if PROP is not
 one of the properties on the list.
"
  (GETF PLIST PROP))

動作は、

(import 'com.informatimago.common-lisp.list:plist-get)

(plist-get '(:foo 0 :bar 1 :baz 2)
           :foo)
;=> 0

というところ

2011-04-18

C.I.CLを眺める(10) PLIST-PUT

| 20:36 | C.I.CLを眺める(10) PLIST-PUT - わだばLisperになる を含むブックマーク はてなブックマーク - C.I.CLを眺める(10) PLIST-PUT - わだばLisperになる

今回は、C.I.CLのlist.lispから PLIST-PUT です。

名前から想像できるようにPLISTにエントリーを加える(もしくは変更する)関数です。

定義は、

(DEFUN PLIST-PUT (PLIST PROP VALUE)
  "
 Change value in PLIST of PROP to VALUE.
 PLIST is a property list, which is a list of the form
 (PROP1 VALUE1 PROP2 VALUE2 ...).  PROP is a symbol and VALUE is any object.
 If PROP is already a property on the list, its value is set to VALUE,
 otherwise the new PROP VALUE pair is added.  The new plist is returned;
 use `(setq x (plist-put x prop val))' to be sure to use the new value.
 The PLIST is modified by side effects.
"
  (SETF (GETF PLIST PROP) VALUE)
  PLIST)

となっています。

使用上の注意が書かれた詳細なドキュメントが付き。

動作は、

(import 'com.informatimago.common-lisp.list:plist-put)

(let ((pl (list :foo 0 :bar 1 :baz 2)))
  (plist-put pl :baz 200)
  pl)
;=> (:FOO 0 :BAR 1 :BAZ 200)

というところです

2011-04-17

C.I.CLを眺める(9) HASHED-INTERSECTION

| 18:23 | C.I.CLを眺める(9) HASHED-INTERSECTION - わだばLisperになる を含むブックマーク はてなブックマーク - C.I.CLを眺める(9) HASHED-INTERSECTION - わだばLisperになる

今回は、C.I.CLのlist.lispから HASHED-INTERSECTION です。

(DEFUN HASHED-INTERSECTION (SET1 SET2)
  "
AUTHORS: Paul F. Dietz <dietz@dls.net>
         Thomas A. Russ <tar@sevak.isi.edu>
"
  (DECLARE (OPTIMIZE SPEED (SAFETY 0) (DEBUG 0))
           (LIST SET1 SET2))
  (LET ((TABLE (MAKE-HASH-TABLE :SIZE (LENGTH SET2)))
        (RESULT NIL))
    (DOLIST (E SET2) (SETF (GETHASH E TABLE) T))
    (DOLIST (E SET1) (WHEN (GETHASH E TABLE)
                       (PUSH E RESULT)
                       (SETF (GETHASH E TABLE) NIL)))
    RESULT))

となっています。

コメントからするとオリジナルの作者は、Paul F. Dietz、 Thomas A. Russの両氏のようです。

仕組みとしては、ハッシュテーブルを利用して共通する部分を抜き出すというもの。

動作は、

(import 'com.informatimago.common-lisp.list:hashed-intersection)

(hashed-intersection '(1 2 3 4)
                     '(1 2 3 4))
;=> (4 3 2 1)

(hashed-intersection '(1 2 3 4 5 6)
                     '(1 2 3 4))
;=> (4 3 2 1)

(hashed-intersection '("1" "2" "3" "4" "5" "6")
                     '("1" "2" "3" "4") )
;=> NIL

というところ。ハッシュの一致判定にデフォルトのEQLが使われているため文字列はスルーされてしまいます。

2011-04-09

*macroexpand-hook*でスタイルチェックの真似事

| 22:36 | *macroexpand-hook*でスタイルチェックの真似事 - わだばLisperになる を含むブックマーク はてなブックマーク - *macroexpand-hook*でスタイルチェックの真似事 - わだばLisperになる

CMUのAIリポジトリを眺めていてlint for clというものを発見しました。

面白そうなのでソースを眺めてみるに、どうやらLISPマシンには、コンパイラにstyle-checkerというものが含まれており、style-checkerにはユーザーが定義できるフックが用意されているため、これを使ってチェックさせています。

ということで、SBCLなどにもそういう層が存在してないだろうかと調べてみましたが、スタイルチェックに関しては、どうやら統一的なレイヤーのようなものはないようで、個別で警告を出しているようです。

それでは悔しいので、どうにか再現できないかと考えてみたのですが、マクロ限定となるものの *macroexpand-hook* にstyle-checkerを登録すれば似たようなことができるのではないか、と思いちょっと試してみました

(defun style-checker (expander form env)
  (awhen (get (car form) :style-checker)
    (funcall it))
  (funcall expander form env))

;; LOOPの場合 
(setf (get 'cl:loop :style-checker)
      (lambda ()
        (format *error-output*
                "~&~A: ~A~%~50:@<~A~>~%"
                'loop
                "\"I consider Loop one of the worst flaws in CL.\""
                "— Paul Graham")))

のようなものを作り、

(setq *macroexpand-hook* 'style-checker)

すると、

(defun foo ()
  (loop :for i :from 0 :to 100 :collect i))
;=> FOO
;-> LOOP: "I consider Loop one of the worst flaws in CL."
;                     — Paul Graham

という感じに文句が出ます。ちなみに警告は、*error-output*に出るので通常の結果とは混ざりません。

もっとナイスでポータブルな方法があったら是非教えて下さい!

2011-04-05

適当にCSVファイルを作成する

| 19:03 | 適当にCSVファイルを作成する - わだばLisperになる を含むブックマーク はてなブックマーク - 適当にCSVファイルを作成する - わだばLisperになる

いつものごとく書き捨てスクリプト的な日常LISPの紹介ですが、今回は趣向を変えてASDFでプロジェクトを作りつつやってみることにします。

タスク: CSVファイルの生成

今回のタスクの内容ですが、Redmineにタスクを登録するのに、csvファイルで一括登録できるプラグインがありまして、それで一括でタスクを登録するためのcsvファイルを作る、というものです。

そもそもRedmineの使い方を知らないんじゃないか等の疑問はありますが、それはおいておきます。

ASDFのプロジェクトを作成する

プロジェクトの作成には、QuickLispで有名な Zach Beane氏作の、quickprojectという便利なものがあるのでこれを使います。

(quickproject:make-project "/foo/bar/redmine-schedule/"
                           :depends-on '(:g000001 :fare-csv :date-calc :lets))

のようなものを実行すると、

/foo/bar以下に、

README.txt
package.lisp
redmine-schedule.asd
redmine-schedule.lisp

のような一式が用意されます。仕組みとしてはシンプルなものですが重宝しています。

ちなみにディレクトリ名の最後のスラッシュが肝なので忘れないようにしましょう。

上記ファイルですが、asdファイルは

(asdf:defsystem #:redmine-schedule
  :serial t
  :depends-on (:g000001 :fare-csv :date-calc :lets)
  :components ((:file "package")
               (:file "redmine-schedule")))

のように生成。:depends-onというのは、依存しているパッケージの指定になります。

package.lispは、:use #:cl位しかされていないので

(defpackage #:redmine-schedule
  (:use #:cl #:lets #:g000001))

のように適当にuseしたいものを追加で配置。

上記で使っているパッケージのざっとした説明ですが、g000001は自分のユーティリティ集で私にしか役に立たないもの、LetSは80年代初期のMacLISPのコードをCLで動くように私が盆栽しているもので完全なる趣味のもの、date-calcはPerlのDate::Calcを範にした日時を扱うのに便利なユーティリティです。

;; 2011/4/4の30日後
(date-calc:add-delta-days 2011 4 4 
                          30)
;=> 2011
;   5
;   4

のようなことができます。

また、fare-csvは、csvファイルとリストを相互変換できる便利ユーティリティです。

ということで、これらを使って、

(defun foo (delta)
  (multiple-value-list
   (date-calc:add-delta-days 2011 4 4 delta)))

(defun every-monday (text)
  (letS* ((d (Erange 1 52)))
    (cons
     '("題名" "説明" "担当者" "開始日" "期限日" "予定工数")
     (Rlist
       (list
        #0=(format nil "~A ~{~A/~A/~A更新~}" text (foo (* 7 d)))
        #0#
        "鈴木"
        (format nil "~{~A~^/~}" (foo (- (* 7 d) 3)))
        (format nil "~{~A~^/~}" (foo (* 7 d)))
        "1")))))

のようなものを書き殴り。

#=と##はコピペ代わりに使えて殴り書きのようなものには便利に使えます。

動作の説明ですが、2011/4/4の次の週から1年分のエントリーを作成するというものです。

再利用を考えてプロジェクトを作成しているのにハードコードな部分がありますが気にしない方向で進みます。

上記のような内容を適当に作成したら、asdファイルをロードしてみます。

そもそも対話的に作っていたりするとロードする必要もなかったりしますが、なんとなくプロジェクトを作っているっぽいという気分の問題です。

asdファイルがASDFに捕捉されていれば、上記プロジェクトの場合、

(asdf:load-system :redmine-schedule)
;; もしくは、
(ql:quickload :redmine-schedule)

でロードできますが、うんともすんとも言わない場合は、めんどくさいので、

(load "/foo/bar/redmine-schedule/redmine-schedule.asd" )

と、asdファイルを直に読み込んでからロードすると、大抵は動きます (あくまでめんどくさい場合)

ロードができたら、上の関数を使ってタスクのエントリーのリストを作成し、それをfare-csvを使ってcsvファイルに書き出します。

(with-> "/tmp/foo.csv"
  (fare-csv:write-csv-lines (every-monday "腹筋") >))

これで

"題名","説明","担当者","開始日","期限日","予定工数"
"腹筋 2011/4/11更新","腹筋 2011/4/11更新","鈴木",2011/4/8,2011/4/11,1
"腹筋 2011/4/18更新","腹筋 2011/4/18更新","鈴木",2011/4/15,2011/4/18,1
"腹筋 2011/4/25更新","腹筋 2011/4/25更新","鈴木",2011/4/22,2011/4/25,1

というファイルができました。

とりあえず簡単にプロジェクトは作れるので、とりあえずASDFでプロジェクトを作成しておいて、また使いたいようなことがあればその時に改良、というのもありかなと思っている最近です。

2011-04-03

MIT CSAIL Tape Archives

| 00:23 | MIT CSAIL Tape Archives - わだばLisperになる を含むブックマーク はてなブックマーク - MIT CSAIL Tape Archives - わだばLisperになる

LetSのコードをいじっていて、MacLISPの PRINENDLINE という変数を調べていた途中でMIT CSAIL Tape Archivesというのを発見しました。

MITのCSAILで80年代に稼動していたTOPS-20のデータを公開しよう、というプロジェクトのようです。

いまのところSICPで有名なサスマン氏等がデータの公開に応じていて、1983年位のMacLISP上で動くScheme処理系(R3RSあたり?)のソースを見ることができる様子。

スタンフォード大学のSAILでも似たようなプロジェクトがありますが、

こういうのは、どしどし公開して貰えると一部のマニアは喜ぶと思います