Lisp中的简单vs复杂用户输入函数

时间:2016-07-11 11:50:59

标签: lisp common-lisp

在此站点:http://www.gigamonkeys.com/book/practical-a-simple-database.html,用户输入功能如下所示:

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

与以下更简单的形式相比,上述功能是否有任何主要优点:

(defun prompt-read2 (prompt)
    (format t "~%~a: " prompt)
    (setf answer (read-line)))

建议始终始终使用force-output*query-io*吗?

2 个答案:

答案 0 :(得分:4)

设置这样的全局变量的答案是不好的。你应该只是回答答案,让调用者用它做你想做的事。如果你使用特殊(〜全局)变量,你应该在名称(*ANSWER*而不是ANSWER)周围添加星号。

需要

FORCE-OUTPUT以确保用户在必须回答之前实际看到提示。如果我在终端中使用SBCL运行第二个版本,程序就会冻结等待输入而不说任何内容。

应该使用

*QUERY-IO*来查询用户的内容,因为某些环境可能希望以不同于其他输出的方式处理。例如,有人可能会为您的程序编写一个GUI包装器,将查询转换为图形对话框。或者他们可能希望将其作为脚本的一部分运行,从字符串提供输入。

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

(defun hello ()
  (format t "~&Hello ~a!~%" (prompt-read "What's your name")))

(defmacro with-input ((input) &body body)
  `(let ((*query-io* (make-two-way-stream (make-string-input-stream ,input)
                                          (make-string-output-stream))))
     ,@body))

(defun test ()
  (with-input ("jkiiski")
    (hello))
  (with-input ("rnso")
    (hello)))
(test)
; Hello jkiiski!
; Hello rnso!

修改

使用SBCL灰色流的更复杂的示例。

(defclass foo-stream (sb-gray:fundamental-character-input-stream)
  ((output-input-script :initarg :script :accessor foo-stream-script)
   (output-stream :initarg :out :accessor foo-stream-out)
   (current-input :initform nil :accessor foo-stream-current-input)))

(defmethod sb-gray:stream-read-char ((stream foo-stream))
  (with-accessors ((input foo-stream-current-input)
                   (out foo-stream-out)
                   (script foo-stream-script)) stream
    (when (or (null input)
              (not (listen input)))
      (let ((output (string-trim '(#\space #\newline)
                                 (get-output-stream-string out))))
        (setf input (make-string-input-stream
                     (format nil "~a~%"
                             (cdr (assoc output script :test #'string=)))))))
    (read-char input)))

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

(defun hello ()
  (format t "~&Hello ~a!~%" (prompt-read "What's your name"))
  (format t "~&I'm ~a too!" (prompt-read "How are you"))
  (format t "~&~a~%" (if (string-equal (prompt-read
                                        "Do you want to delete all your files")
                                       "yes")
                         "Deleting all files... (not really)"
                         "Not deleting anything.")))

(defmacro with-input-script ((script) &body body)
  (let ((out-sym (gensym "out")))
    `(let* ((,out-sym (make-string-output-stream))
            (*query-io* (make-two-way-stream
                         (make-instance 'foo-stream
                                        :out ,out-sym
                                        :script ,script)
                         ,out-sym)))
       ,@body)))

(defun test ()
  (with-input-script ('(("What's your name:" . "jkiiski")
                        ("How are you:" . "great")
                        ("Do you want to delete all your files:" . "No")))
    (hello))
  (with-input-script ('(("What's your name:" . "Foo Bar")
                        ("How are you:" . "fine")
                        ("Do you want to delete all your files:" . "Yes")))
    (hello)))

(test)
; Hello jkiiski!
; I'm great too!
; Not deleting anything.
; Hello Foo Bar!
; I'm fine too!
; Deleting all files... (not really)

答案 1 :(得分:2)

是的,你的代码很容易,但第一个更清楚你在做什么:

  

* query-io *是一个全局变量(由于全局变量的*命名约定,你可以告诉它)包含输入流   连接到终端。 prompt-read的返回值将是   最后一个表单的值,对READ-LINE的调用,返回   它读取的字符串(没有尾随换行符。)

这就是他们所说的* query-io *

关于你可以放在那里的溪流如下:

  

大多数其他I / O函数也接受T和NIL作为流指示符   但具有不同的含义:作为流指示符,T表示   双向流* TERMINAL-IO *,而NIL指定   * STANDARD-OUTPUT *作为输出流,* STANDARD-INPUT *作为输入流

在这种情况下,它似乎只指向* standard-input *而不指向双向流t