我正在使用 Emacs、Slime 和 SBCL。簡化我面臨的問題,假設我有這個功能作業:
(defun get-answer (x y z)
(format t "Which animal would you like to be: ~s ~s ~s ~%" x y z)
(let ((answer (read-line)))
(cond ((equal answer x) (format t "user picks ~s" x))
((equal answer y) (format t "user picks ~s" y))
((equal answer z) (format t "user picks ~s" z)))))
它適用于固定數量的輸入:
CL-USER> (get-answer "fish" "cat" "owl")
Which animal would you like to be: "fish" "cat" "owl"
fish
user picks "fish"
NIL
我想概括這個函式,撰寫一個變體引數宏來構建一個變體數量的cond子句。基本上,Common Lisp 宏會為我撰寫 cond 子句:)
例如,我希望我可以這樣稱呼它:
CL-USER> (get-answer '("pig" "zebra" "lion" "dog" "cat" "shark"))
要不就:
CL-USER> (get-answer '("dog" "cat"))
無論哪種方式,它都會分別生成 6 個和 2 個適當cond的子句。我嘗試構建一些東西來實作這個目標。
我專注于cond條款部分的草稿/草圖是:
(defmacro macro-get-answer (args)
`(cond
(,@(mapcar (lambda (str body)
(let ((str (first str body))
(body (second str body)))
`((string= answer ,str)
,body)))
args))))
該變數answer應該保存用戶插入的值。但是,我無法讓它作業。slime-macroexpand-1并沒有真正的幫助。
作為錯誤訊息,我得到:
The value
QUOTE
is not of type
LIST
這不是我所期待的。有沒有辦法解決這個問題?
謝謝。
uj5u.com熱心網友回復:
我認為您不需要宏。請注意,存在 的重復模式((equal answer x) (format t "user picks ~s" x)),因此您應該考慮如何簡化它。
因此,這是您的功能和預期輸入:
(defun get-answer (x y z)
(format t "Which animal would you like to be: ~s ~s ~s ~%" x y z)
(let ((answer (read-line)))
(cond ((equal answer x) (format t "user picks ~s" x))
((equal answer y) (format t "user picks ~s" y))
((equal answer z) (format t "user picks ~s" z)))))
(get-answer '("pig" "zebra" "lion" "dog" "cat" "shark"))
(get-answer '("dog" "cat"))
我會寫這樣的東西:
(defun get-answer (args)
(format t "Which animal would you like to be: ~{~s ~} ~%" args)
(let ((answer (read-line)))
(when (member answer args :test #'string=)
(format t "User picks ~s" answer)
answer)))
測驗:
(get-answer '("pig" "zebra" "lion" "dog" "cat" "shark"))
(get-answer '("dog" "cat"))
請注意,您的函式總是回傳NIL,我的回傳選擇的字串或NIL. 收到用戶輸入后,您可能不想立即丟棄它。
如果你有帶值的字串,像這樣:
(get-answer '(("pig" 1) ("zebra" 3) ("lion" 2) ("dog" 8) ("cat" 12) ("shark" 20)))
我會使用find:
(defun get-answer (args)
(format t "Which animal would you like to be: ~{~s ~} ~%" (mapcar #'first args))
(let* ((answer (read-line))
(match (find answer args :test #'string= :key #'car)))
(when match
(format t "User picks ~s " (first match))
(second match))))
此函式回傳NIL或選擇動物的值。
uj5u.com熱心網友回復:
正如Martin P?da 的回答一樣:這不是一個需要宏的好地方。特別是如果您想要一些不確定數量的選項,請傳遞它們的串列:Common Lisp 具有非常好的串列處理功能。
在您給出的簡單情況下,該函式變得幾乎微不足道:
(defun get-answer (options)
(format t "Which animal would you like to be (from ~{~A~^, ~})? " options)
(let ((selected (car (member (read-line) options :test #'string-equal))))
(format t "picked ~S~%" (or selected "(a bad answer)"))
selected))
如果您想要將用戶選擇的值映射到另一個值,那么您想要的是 alist 或 plist:
(defun get-answer/map (option-map)
(format t "Which animal would you like to be (from ~{~A~^, ~})? "
(mapcar #'car option-map))
(let ((selected (assoc (read-line) option-map :test #'string-equal)))
(format t "picked ~A~%" (if selected (car selected) "(a bad value)"))
;; Second value tells us if fn succeeded, as first can be NIL
(values (cdr selected) selected)))
和之類的函式的全部目的是在串列中搜索條目:通過撰寫大量子句來費力地重新實作它們的半作業版本是沒有用的,無論您是否自動執行此操作。memberassoccond
現在,人們經常說“但我想學習宏,所以我將首先學習如何將函式重寫為宏”。 這不是從哪里開始的:宏對于以這種方式重寫函式從來沒有用處,并且認為它們是非常有害的。
這是一個無法正常作業的簡單示例:
(get-answer (read-options-from-file *my-options-file*))
如果get-answer是一個宏,這意味著如何作業?選項的數量不僅在宏擴展時是未知的,甚至在原則上也是不可知的,因為在編譯時不可能知道檔案名及其內容。
宏不是函式的替代品:它們是語言之間的函式:宏將 CL( 其他宏) 宏理解的語言編譯成 CL( 其他宏)的語言。
我有點難以想到宏在這里可能有用的情況,但也許我們可以想象一種具有以下with-answer-options形式的語言:
(with-answer-options (a)
("elephant" (explode-world :because-of a))
("fish" (set-fire-to-computer :blaming a)))
請注意,這種形式現在看起來與函式呼叫完全不同:它是一種稍微擴展了 CL 的小語言,并將通過以下宏映射到 CL:
(defmacro with-answer-options ((var &optional (prompt "Pick an answer"))
&body clauses)
(unless (symbolp var)
(error "~S should be a variable" var))
(let ((choices (mapcar #'car clauses)))
(unless (every #'stringp choices)
(error "choices should be (<literal string> form ...)"))
`(let ((,var (progn (format t "~A from ~{~A~^ ~}? "
,prompt ',choices)
(read-line))))
(cond
,@(mapcar (lambda (clause)
(destructuring-bind (val &body body) clause
`((string-equal ,var ',val)
,@body)))
clauses)
(t (error "~A is not one of ~{~A~^ ~}" ,var ',choices))))))
所以現在:
> (with-answer-options (a "Pick a bad thing")
("elephant" (explode-world :because-of a))
("fish" (set-fire-to-computer :blaming a)))
Pick a bad thing from elephant fish? frog
Error: frog is not one of elephant fish
uj5u.com熱心網友回復:
這個問題有點奇怪,但也許重點只是為了了解Common Lisp的宏。人們可以首先計算條件的成員。這意味著,生成一個string=表單串列。然后只回傳一個包含cond特殊形式的串列和((string= str) return-value).
(defmacro get-answer (answers)
(let ((clauses (mapcar (lambda (str body)
(let ((str (first str body))
(body (second str body)))
`((string= ,str) ,body))) answers)))
`(cond ,@clauses)))
要測驗宏,可以使用macroexpand和macroexpand-1。
> (macroexpand-1 '(get-answer (("test1" 1) ("test2" 2))))
(COND
((STRING= "test1") 1)
((STRING= "test2") 2))
但我個人認為它不值得一個宏。我認為以下功能沒有問題:
(defun get-answer (answer)
(cond
((string= answer "dog") 6)
((string= answer "cat") 2)))
uj5u.com熱心網友回復:
有一個引號,因為您的宏表單包含一個:
CL-USER 8 > (defmacro macro-get-answer (args) (print args) nil)
MACRO-GET-ANSWER
CL-USER 9 > (macro-get-answer '(1 2 3))
(QUOTE (1 2 3))
NIL
CL-USER 10 > (macro-get-answer (quote (1 2 3)))
(QUOTE (1 2 3))
NIL
可能的解決方案:
不要使用報價
洗掉報價
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/466914.html
