首页

2010年5月29日星期六

comment::write a simple database..

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;取得列表里面的值使用:标记符和标记名称 如
;;                      (getf (list :a 2 :b 4 c: 6) :a)
;;

(defun make-getf (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))


;;
;;定义全局变量使用defvar函数,变量名使用(*)包围起来给以命名,如*db*
;;

(defvar *db* nil)

;;使用宏push命令添加数据到*db*

(defun add-record(cd)(push cd *db*))

;;将使用定义的add-record函数增加记录到数据库

(add-record (make-getf "Roses" "Kathy Mattea" 7 t))
(add-record (make-getf "Hit" "Kathy M" 8 t))
(add-record (make-getf "Row" "KMattea" 9 t))

;;访问global variables *db* 目前的数据信息看上去很乱
;;不易查看,这样也成为了一个垃圾数据,因此改写函数如下
;;使用dolist宏命令循环*db*的所有成员值直到结束,绑定每个成员的值到cd中,
;;cd的每一个值用format来打印.
;;The ~a directive is the aesthetic directive; it means
;;to consume one argument and output it in a
;;human-readable form. This will render keywords without
;;the leading : and strings without quotation marks.
;;~t制表,~10t向右移动10位空位
;;Now things get slightly more complicated. When FORMAT
;;sees ~{ the next argument to be consumed must be a list.
;;FORMAT loops over that list, processing the directives
;;between the ~{ and ~}, consuming as many elements of the
;;list as needed each time through the list. In dump-db,
;;the FORMAT loop will consume one keyword and one value from
;;the list each time through the loop. The ~% directive doesn’
;;t consume any arguments but tells FORMAT to emit a newline.
;;Then after the ~} ends the loop, the last ~% tells FORMAT to
;;emit one more newline to put a blank line between each CD.

(defun dump-db()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))

;;为add-record改写函数,因为只有我们自己知道如何使用
;;add-record添加数据,换成其他人就不方便了
;;~%这些是格式化字符串format的
;;Note that there’s no ~% in the format string, so
;;the cursor will stay on the same line. The call to
;;FORCE-OUTPUT is necessary in some implementations
;;to ensure that Lisp doesn’t wait for a newline
;;before it prints the prompt.


;;Then you can read a single line of text with the aptly
;;named READ-LINE function. The
;;variable *query-io* is a global variable (which you can
;;tell because of the * naming convention
;;for global variables) that contains the input stream
;;connected to the terminal. The return value of prompt-read
;;will be the value of the last form, the call to READ-LINE,
;;which returns the string it read (without the trailing newline.)
;;注意*query-io* 是一个输入流,同样是一个全局变量

(defun prompt-read (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))

;;You can combine your existing make-cd function with prompt-read to
;;build a function thatmakes a new CD record from data it gets by
;;prompting for each value in turn.

(defun prompt-for-cd ()
(make-getf
(prompt-read "Title")
(prompt-read "Artist")
(prompt-read "Rating")
(prompt-read "Ripped [y/n]")))

;; That’s almost right. Except prompt-read returns a string, which,
;;while fine for the Title and Artist fields, isn’t so great for the
;;Rating and Ripped fields, which should be a number and a boolean.
;;Depending on how sophisticated a user interface you want, you can go
;;to arbitrary lengths to validate the data the user enters. For now let's
;;lean toward the quick and dirty: you can wrap the prompt-read for the
;;rating in a call to Lisp’s PARSE-INTEGER function, like this:
;;使用Lisp's parse-integer函数限定此处是数字,而不是字符串,如果是字符串即返回NIL

(parse-integer (prompt-read "Rating"))


;;Unfortunately, the default behavior of PARSE-INTEGER is to signal an
;;error if it can’t parse an integer out of the string or if there's
;;any non-numeric junk in the string. However, it takes an optional keyword
;;argument :junk-allowed, which tells it to relax a bit.
;;很不幸,这里缺省的PARSE-INTEGER 将返回NIL而不是一个数字,判断解析输入的string是不是整数
;;如果条件不成立就丢弃这个string,或者理解为如果在那里是非数字将丢弃那些string

(parse-integer (prompt-read "Rating") :junk-allowed t)

;;但是这里还有一个问题,如果它在junk里找到的不是一个整数,parse返回NIL,而不是
;;一个number.我们需要他返回一个数字而不是NIL,在这里刚好需要Lisp's OR 宏来处理这些事情,
;;它相似C、Python、java中的“short-circuiting”, ( or NIL if they’re all NIL )
;;这样你就得到了缺省值0,用在用户输入错误,误将字符串(字符)当数字输入,自动将字符串
;;(字符)转换为缺省值0

(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)


;;Fixing the code to prompt for Ripped is quite a bit simpler. You can just use
;;the Common Lisp function Y-OR-N-P.
;;使用Lisp's y-or-n-p函数来表示布尔值true and false.
;;类似于c、java、python等语言里面的控制流的表达式

(y-or-n-p "Ripped [y/n]: ")

;;将前面的函数拼凑起来,将得到一个健全的新函数prompt-for-cd,(函数名自定义)

(defun prompt-for-cd ()
(make-getf
(prompt-read "Title")
(prompt-read "Artist")
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
(y-or-n-p "Ripped [y/n]: ")))


;;最后,我们将完成添加数据的界面,将prompt-for-cd函数构造成一个新函数,
;;(嘿,看这里用到了类似面向对象的封装概念哦,其实这是一个函数调用,
;;如C语言里面定义一个foo()函数,然后在main里面写一个控制流来控制循环次数,
;;来调用foo()直到条件为假的时候退出循环停止调用,所以不要把这两者搞混了,
;;面向对象编程语言的封装则是把类封装成一个对象,其Lisp是一个函数式编程语言
;;,但具有面向对象的思想,具体请参考有关面向对象程序设计思想的书籍,本人也只
;;是一知半解,如有好的程序设计思想书籍,不要吝啬,推荐给我一本....)
;;
;;
;;使用LOOP宏的简单形式,循环重复执行主要语法直到退出,然后return

;;等到新构造的函数add-cds,执行该函数,进行数据添加,需要查看其数据使用前面定义
;;定义的函数dump-db

(defun add-cds ()
(loop (add-record (prompt-for-cd))
(if (not (y-or-n-p "Continue? [y/n]: ")) (return))))

;;with-open-file 宏打开一个文件将这些流绑定一个变量,完成固定的表达式,
;;和关闭这个文件,当然他也尝试做一些错误处理,属于with-open-file的语法定
;;义的一部分,它包含变量名,文件流,with-open-file的主体一个变量必须是
;;一个文件名,和如何控制一些选项打开文件,在这用:direction :output具体指
;;明打开的文件和写入和改写现存文件,存在同名用:if-exists :supersede
;;打印数据库的内容用(print *db* out)不同format,with-standard-io-syntax
;;保证运行状态,打印标准值

;;这个是CLTL2里面所介绍with-standard-io-syntax的用法,比较详细
;;with-standard-io-syntax {declaration}* {form}*

;;X3J13 voted in June 1989 (DATA-IO)   to add the macro with-standard-io-syntax.
;;Within the dynamic extent of the body, all reader/printer controlvariables,
;;including any implementation-defined ones not specified byCommon Lisp, are
;;bound to values that produce standard read/printbehavior. Table 22-7 shows
;;the values to which standard Common Lisp variables are bound.

;;The values returned by with-standard-io-syntax are the values of the last body
;;form, or nil if there are no body forms.

;;The intent is that a pair of executions, as shown in the following example,
;;should provide reasonable reliable communication of data from one Lisp process
;;to another:

;;; Write DATA to a file.
;;(with-open-file (file pathname :direction :output)
;;  (with-standard-io-syntax
;;    (print data file)))

;;; ...  Later, in another Lisp:
;;(with-open-file (file pathname :direction :input)
;;  (with-standard-io-syntax
;;    (setq data (read file))))

;;Using with-standard-io-syntax to bind all the variables, instead of using let and
;;explicit bindings, ensures that nothing is overlooked and avoids problems with
;;implementation-defined reader/printer control variables. If the user wishes to use
;;a non-standard value for some variable, such as *package* or *read-eval*, it can
;;be bound by let inside the body of with-standard-io-syntax. For example:

;;; Write DATA to a file.  Forbid use of #. syntax.
;;(with-open-file (file pathname :direction :output)
;;  (let ((*read-eval* nil))
;;    (with-standard-io-syntax
;;      (print data file))))

;;; Read DATA from a file.  Forbid use of #. syntax.
;;(with-open-file (file pathname :direction :input)
;;   (let ((*read-eval* nil))
;;    (with-standard-io-syntax
;;     (setq data (read file)))))

;;Similarly, a user who dislikes the arbitrary choice of values for *print-circle* and
;;*print-pretty* can bind these variables to other values inside the body.

;;The X3J13 vote left it unclear whether with-standard-io-syntax permits declarations
;;to appear before the body of the macro call. I believe that was the intent, and this
;;is reflected in the syntax shown above; but this is only my interpretation.

;;(defun save-db (filename)
;;  (with-open-file (out filename
;;                   :direction :output
;;           :if-exists :supersede)
;;    (with-standard-io-syntax
;;      (print *db* out))))


;;现在不需要具体指定 WITH-OPEN-FILE 的选项:direction,以后需要:input这个缺省值
;;使用函数read来读取文件流.

(defun load-db (filename)
(with-open-file (in filename)
(with-standard-io-syntax
(setf *db* (read in)))))


;;;;;(添加和加载数据库完成了,我们还需要查询数据)

;;使用remove-if-not函数匹配条件和数据,并且返回一个list
;;cltl2里面该函数的表达式
;;    delete-if-not predicate sequence &key :from-end
;;            :start :end :count :key
;;它创建了一个新的list,但并未修改原始的list,它是搜索匹配一个文件的字符数据
;;接受条件后返回true and false.#'evenp是一个匿名函数lambda的速记写法,如果这里
;;没有#'那么lisp将这个evenp看作是一个变量名或一个变量值

(remove-if-not #'evenp '(1 2 3 4 5 6 7 8 9 10))

;;使用getf 来取出*db*数据列表里的名字,使用equal进行比较,无论结果怎么样都要构造lambda表达式
;;传递到remove-if-not

(remove-if-not
#'(lambda (cd) (equal (getf cd :artist) "Dixie Chicks")) *db*)

;;现在假设要将所有表达完整的封装在一个函数里

(defun select-by-artist (artist)
(remove-if-not
#'(lambda (cd) (equal (getf cd :artist) artist))
*db*))

;;那么这样查询数据只能查出一条信息,然而要想查出其他信息,将要写几个甚至更多和select-by-artist
;;类似的函数,除了内容不同其他都一样属于一个匿名函数,将select函数改为更综合的可变函数

(defun select (selector-fn)
(remove-if-not selector-fn *db*))

;;So what happened to the #'? Well, in this case you don’t want REMOVE-IF-NOT to use the
;;function named selector-fn. You want it to use the anonymous function that was passed as
;;an argument to select in the variable selector-fn. Though, the #' comes back in the call
;;to select.

(select #'(lambda (cd) (equal (getf cd :artist) "Dixie Chicks")))

;;But that’s really quite gross-looking. Luckily, you can wrap up the creation of the anonymous
;;function.

(defun artist-selector (artist)
#'(lambda (cd) (equal (getf cd :artist) artist)))

;;This is a function that returns a function and one that references a variable that—it seems—
;;won’t exist after artist-selector returns.6 It may seem odd now, but it actually works just the
;;way you’d want—if you call artist-selector with an argument of "Dixie Chicks", you get an
;;anonymous function that matches CDs whose :artist field is "Dixie Chicks"

(select (artist-selector "Dixie Chicks"))


;;has three parameters, a, b, and c, and must be called with three arguments. But sometimes you
;;may want to write a function that can be called with varying numbers of arguments. Keyword
;;parameters are one way to achieve this. A version of foo that uses keyword parameters might
;;look like this:
;;这个可变的列表唯一的差异在参数开始,&key关键字

(defun foo (&key a b c) (list a b c))

;;Normally if a function is called with no argument for a particular keyword parameter, the
;;parameter will have the value NIL. However, sometimes you’ll want to be able to distinguish
;;between a NIL that was explicitly passed as the argument to a keyword parameter and the default
;;value NIL. To allow this, when you specify a keyword parameter you can replace the simple name
;;with a list consisting of the name of the parameter, a default value, and another parameter
;;name, called a supplied-p parameter. The supplied-p parameter will be set to true or false
;;depending on whether an argument was actually passed for that keyword parameter in a
;;particular call to the function. Here’s a version of foo that uses this feature:
;;这些值跟变量的边界合符,个别参数没有值,将这些符合变量设置为NIL

(defun foo (&key a (b 20) (c 30 c-p)) (list a b c c-p))

;;这个函数返回匿名函数返回一条子句逻辑与
(defun where (&key title artist rating (ripped nil ripped-p))
#'(lambda (cd)
(and
(if title (equal (getf cd :title) title) t)
(if artist (equal (getf cd :artist) artist) t)
(if rating (equal (getf cd :rating) rating) t)
(if ripped-p (equal (getf cd :ripped) ripped) t))))



(defun update (selector-fn &key title artist rating (ripped nil ripped-p))
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(if title (setf (getf row :title) title))
(if artist (setf (getf row :artist) artist))
(if rating (setf (getf row :rating) rating))
(if ripped-p (setf (getf row :ripped) ripped)))
row) *db*)))

(defun delete-rows (selector-fn)
(setf *db* (remove-if selector-fn *db*)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(select
#'(lambda (cd)
(and (equal (getf cd :title) "Give Us a Break")
(equal (getf cd :ripped) t))))


;;新函数reverse相反,例如引用一个列表(reverse '(1 2 3 4))它将返回一个反序排列的列表
;;defmacro跟defun一样是同样属于定义一个宏和函数,但defmacro是defun的一个扩展,它同样
;;可以define a function并且有参数列表,具体区别请参考cltl2-note98是macro的详细介绍

(defmacro backwards (expr) (reverse expr))

;;;;;'(quote)引用 `准引用


(defun make-comparison-expr (field value)
(list 'equal (list 'getf 'cd field) value))

;;;It turns out that there’s an even better way to do it. What you’d really like is a way to write
;;;;an expression that’s mostly not evaluated and then have some way to pick out a few expressions
;;;;that you do want evaluated. And, of course, there’s just such a mechanism. A back quote (`)
;;;;before an expression stops evaluation just like a forward quote.
;;CL-USER> `(1 2 3)
;;(1 2 3)
;;CL-USER> '(1 2 3)
;;(1 2 3)
;;;;However, in a back-quoted expression, any subexpression that’s preceded by a comma is
;;;;evaluated. Notice the effect of the comma in the second expression:
;;`(1 2 (+ 1 2)) → (1 2 (+ 1 2))
;;`(1 2 ,(+ 1 2)) → (1 2 3)

(defun make-comparison-expr (field value)
`(equal (getf cd ,field) ,value))

;;The POP macro performs the inverse operation of the PUSH macro you used to add records to *db*

;;;;CLTL2这样解释collecting:
;;During each iteration, these constructs collect the value of the specified expression into a list.
;;When iteration terminates, the list is returned.

;;The argument var is set to the list of collected values; if var is specified, the loop does not
;;return the final list automatically. If var is not specified, it is equivalent to specifying an
;;internal name for var and returning its value in a finally clause. The var argument is bound as
;;if by the construct with. You cannot specify a data type for var; it must be of type list.


(defun make-comparisons-list (fields)
(loop while fields
collecting (make-comparison-expr (pop fields) (pop fields))))


;;This macro uses a variant of , (namely, the ,@) before the call to make-comparisons-list.
;;The ,@ “splices” the value of the following expression—which must evaluate to a list—into the
;;enclosing list.

(defmacro where (&rest clauses)
`#'(lambda (cd) (and ,@(make-comparisons-list clauses))))

;;You can see exactly what code a call to where will generate using the function MACROEXPAND-1.
;;关于函数macroexpand-1参考cltl2-note99

(macroexpand-1 '(where :title "Give Us a Break" :ripped t))
#'(LAMBDA (CD)
(AND (EQUAL (GETF CD :TITLE) "Give Us a Break")
(EQUAL (GETF CD :RIPPED) T)))



;;;;;;;;;;how do you know when a function or method is too big? ;;;;;;;;;;;;;;;;;;;;;;;;,;;;;;;;;
;;;;;;;;;;;;I don’t like any method to be bigger than my head.;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

没有评论:

发表评论