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 |

2007-10-21


リーダーマクロでPerlのインデックスのまね事

| 13:01 | リーダーマクロでPerlのインデックスのまね事 - わだばLisperになる を含むブックマーク はてなブックマーク - リーダーマクロでPerlのインデックスのまね事 - わだばLisperになる

今回は、Perlの$foo[1]や、$bar[i]などという、インデックス指定の構文を真似してみることにしました。

作ってみたい構文

(let ((list '(0 1 2 3)))
  [1]list)
;=> 1

最初は、ディスパッチマクロ文字が引数を取れることを利用して組んでみましたが、これだと、10進の数しか引数に取れず変数が使えないのでは面白くないのでリーダーマクロで作ることに方針変更。

試行錯誤しているうちにできたものの、なんだか良く分からないものができました。

(set-macro-character #\[
		     (lambda (stream char)
		       (declare (ignore char))
		       (cache-arg (read stream t nil t))
		       (values)))

(set-macro-character #\]
		     (lambda (stream char)
		       (declare (ignore char))
		       (list 'elt-with-cached-index (read stream t nil t))))

(let (arg)
  (defun cache-arg (n)
    (setq arg n))
  (defmacro elt-with-cached-index (lst)
    `(elt ,lst ,arg)))
[1]list

というのを単純に

(elt list 1)

に変換したかっただけなのですが、いまいちうまい展開方法が分からず、

  1. #\[は、cache-argという次の文字をキャッシュする関数を実行するが、副作用が欲しいだけで、何かが展開されると逆に不都合なので、(values)を実行し、何も痕跡を残さない。
  2. #\]は、chache-argがキャッシュした引数でnthを実行するという、elt-with-cached-indexに展開させる。
  3. cache-argからelt-with-cached-indexに引数を受け渡す手段がないので、両者をクロージャで包み変数を共有させた。
  4. nth-with-cached-indexは展開された引数が、展開される文脈中で実行される必要があるので?(うまく説明できない(;´Д`)…)マクロにした。

という流れです。特にリーダーマクロが(values)を最後に評価して消えたりするところが非常にアレな気がします。

;; 動作
(progn
  [1]'(1 2 3))
; => 2

(my i 3
  (print [(+ i 3)](srfi:iota 10)))
; => 5

(my str "123456789"
  [3]str)
; => 4

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

(map 'string (lambda (x) [x]"abcdefghij")
     '(2 3 8 0 1 2 3))
; => "cdiabcd"  

(loop :for i :below 10 
      :do (print [i]'(a b c d e f g i h j k)))
; =>
;A 
;B 
;C 
;D 
;E 
;F 
;G 
;I 
;H 
;J 
;NIL 

ちなみに

(my lst '(foo bar baz)
 [1

 (print "hello")

 ]lst)
; -> "hello"
; => BAR

のようなものの対策は施していません。

雑感

自分の期待とは裏腹に使えなさそうなものに仕上がりました。

こういう場合の定番の解決手法ってどういうものがあるんでしょう…。

Paul Graham氏のユーティリティ その8

| 05:00 | Paul Graham氏のユーティリティ その8 - わだばLisperになる を含むブックマーク はてなブックマーク - Paul Graham氏のユーティリティ その8 - わだばLisperになる

PG氏のユーティリティを読んでみることの8回目。

文字列処理のユーティリティ編です。

Lisp utilities that were not included in On Lisp or ANSI Common Lisp

お題

read-as-string

暗記で再現

(defun read-as-string (in)
  (with-output-to-string (out)
    (do ((c (read-char in nil :eof) (read-char in nil :eof)))
	((eql c :eof))
      (write-char c out))))

できた。streamを文字列にして返すものらしい。

;; 動作
(with-input-from-string (str "fooooooooo barrrrr  bazzzz")
  (read-as-string str))
;=>"fooooooooo barrrrr  bazzzz"

お題

read-list-from-string

暗記で再現

(defun read-list-from-string (str &optional (start 0))
  (bind (var pos) (read-from-string str nil :eof :start start)
	(if (eql var :eof)
	    nil
	    (cons var (read-list-from-string str pos)))))

できた。文字列を読み込んでリストにして返すものらしい。

;; 動作
(read-list-from-string "foo bar baz quux zot")
;=>(FOO BAR BAZ QUUX ZOT) 

お題

read-numbers-from-string

暗記で再現

(defun read-numbers-from-string (str &optional (start 0))
  (bind (var pos) (ignore-errors 
		    (read-from-string str nil :eof :start start))
	(if (numberp var)
	    (cons var (read-numbers-from-string str pos))
	    nil)))

できた。文字列に現われた最初の数字群をリストにして返すものらしい。途中で数字以外が出現すると、そこで打ち切りとなる。

;; 動作
(read-numbers-from-string "foo bar baz 1 2 3 100")
=>nil
(read-numbers-from-string "1 2 3 100 foo 200")
=>(1 2 3 100) 

お題

read-number-from-string

暗記で再現

(defun read-number-from-string (str)
  (let ((n (ignore-errors (read-from-string str nil :eof)))) ; nilと、:eof不要
    (if (numberp n) n nil)))

今一歩。今度は最初の一文字を読むもの。最初の一文字を読めば良いので、(read-from-string str)だけで良かった。

;; 動作
(read-number-from-string "foo")
;=>nil
(read-number-from-string " 1 2 3")
;=> 1
(read-number-from-string "foo 1 2 3")
;=> nil

お題

string->hex

暗記で再現

(defun string->hex (str)
  (with-output-to-string (out)
    (dotimes (i (length str))
      (let ((c (char str i)))
	(format out "~2,'0X" (char-code c))))))

できた。文字列を16進のキャラクタコードの文字列にして返すもの。

;; 動作
(string->hex "a")
;=>"61"

お題

hex->string

暗記で再現

(defun hex->string (str)
  (if (evenp (length str))
      (let ((*read-base* 16))
	(with-output-to-string (out)
	  (dotimes (i (/ (length str) 2))
	    (princ (code-char (read-from-string str nil nil
						:start (* i 2)
						:end (+ (* i 2) 2)))
		   out))))
      (error "odd length hex string: ~A.~%" str)))

できた。その名の通りstring->hexの逆の動作をするもの。なかなかややこしい。

当然なのかもしれないけれど、これで日本語を扱うには少し問題があるっぽい。

;; 動作
(hex->string "61")
;=> "a"
(hex->string (string->hex "日本語 BAR baZ")))
"e〓g,Šž BAR baZ"