对于一个课程项目,我必须用Lisp编写程序。
程序应包含最重要的lisp函数,其输入和输出参数以及可能的可选参数。
例如:函数-首先,输入-列表,输出-对象(列表的第一个成员)。
程序应以两种不同的方式工作:
您为程序指定一个函数名称,它应该返回函数参数。
您输入函数参数,如果存在带有这些参数的函数,则应返回该函数的名称。
我的问题:
在Lisp中处理这样的任务的正确方法是什么?我认为也许一棵树将是处理它的方法? (用所有函数和参数创建一棵树,然后编写一个处理它的程序)。
有没有比这更好的主意了?或从哪里/如何开始的一些建议?还是包含任何信息的教程?
此刻,我有点不知道如何开始。您能提供的任何帮助将不胜感激。
英语不是我的母语,所以我希望一切都可以理解。
问候。
答案 0 :(得分:2)
首先来看一下准备常见的Lisp开发环境。之后,我认为您应该进行调查:
使用defun,
之类的东西。首先看一下两个常见的lisp函数:
这是一个小例子:
CL-USER> (defun my-sum (a b) "Add my-sum parameters A and B." (+ a b))
MY-SUM
CL-USER> (my-sum 2 3)
5 (3 bits, #x5, #o5, #b101)
CL-USER> (describe #'my-sum)
#<FUNCTION MY-SUM>
[compiled function]
Lambda-list: (A B)
Derived type: (FUNCTION (T T) (VALUES NUMBER &OPTIONAL))
Documentation:
Add my-sum parameters A and B.
Source form:
(SB-INT:NAMED-LAMBDA MY-SUM
(A B)
"Add my-sum parameters A and B."
(BLOCK MY-SUM (+ A B)))
; No values
CL-USER> (documentation 'my-sum 'function)
"Add my-sum parameters A and B."
CL-USER> (defun my-sum (a b) "Add my-sum parameters A and B." (declare (type fixnum a b)) (+ a b))
WARNING: redefining COMMON-LISP-USER::MY-SUM in DEFUN
MY-SUM
CL-USER> (describe #'my-sum)
#<FUNCTION MY-SUM>
[compiled function]
Lambda-list: (A B)
Derived type: (FUNCTION (FIXNUM FIXNUM)
(VALUES
(INTEGER -9223372036854775808 9223372036854775806)
&OPTIONAL))
Documentation:
Add my-sum parameters A and B.
Source form:
(SB-INT:NAMED-LAMBDA MY-SUM
(A B)
"Add my-sum parameters A and B."
(DECLARE (TYPE FIXNUM A B))
(BLOCK MY-SUM (+ A B)))
; No values
最后,最后一个技巧是处理describe输出的字符串:
CL-USER> (with-output-to-string (*standard-output*)
(describe #'my-sum))
"#<FUNCTION MY-SUM>
[compiled function]
Lambda-list: (A B)
Derived type: (FUNCTION (FIXNUM FIXNUM)
(VALUES
(INTEGER -9223372036854775808 9223372036854775806)
&OPTIONAL))
Documentation:
Add my-sum parameters A and B.
Source form:
(SB-INT:NAMED-LAMBDA MY-SUM
(A B)
\"Add my-sum parameters A and B.\"
(DECLARE (TYPE FIXNUM A B))
(BLOCK MY-SUM (+ A B)))
"
答案 1 :(得分:2)
从表面上看,任务似乎是在内存中构造一个简单的符号数据库,可通过两种方式进行搜索。数据库中的条目应理解为功能。 “输出参数”可能被理解为一个或多个返回值。这些东西在ANSI Lisp中没有命名。对该任务的有用解释是无论如何都要给返回值提供符号标签。此外,我们也许可以将类型符号用于返回值和参数。因此,例如, cons 函数的数据库条目可能类似于:
(cons (t t) cons) ;; function named cons takes two objects, returns a cons
类型t
是ANSI Lisp中所有类型的超类型;意思是“任何值”。
可以将此类记录的列表放入某些全局变量中。然后,我们编写一个可能名为get-params-by-name
的函数,使得:
(get-params-by-name 'cons) -> (t t)
和另一个:get-names-by-params
:
(get-names-by-params '(t t)) -> (cons)
此函数以列表的形式返回所有匹配的函数。多个功能可以具有此签名。
然后,诀窍是找到可选参数和rest参数的良好表示形式。该语言可能使用的是同一符号:
(list (&rest t) list) ;; list takes rest arguments of any type, returns list
由于我们仅对精确匹配感兴趣,因此我们不必实际解析&rest
表示法。当用户按参数查询时,使用相同的语法,他们的查询对象实际上是(&rest t)
。
equal
函数可用于判断两个符号列表是否相同:
(equal '(&rest t) '(&rest t)) -> t
(equal '(t t) '(t t)) -> nil
因此练习并不困难:只需通过列表进行映射,寻找匹配项即可。
(defun get-name-by-params (database params)
(let ((matching-entries (remove-if-not (lambda (entry)
(equal (second entry) params))
database)))
(mapcar #'first matching-entries))) ;; just the names, please
此处,该函数将数据库列表作为参数,而不是引用全局变量。我们将其集成到其中的整个程序可以提供其他接口,但这是我们的低级查找功能。
测试:
[1]> (get-name-by-params '((cons (t t) cons) (list (&rest t) list)) '(integer string))
NIL
[3]> (get-name-by-params '((cons (t t) cons) (list (&rest t) list)) '(t t))
(CONS)
[4]> (get-name-by-params '((cons (t t) cons) (list (&rest t) list)) '(&rest t))
(LIST)
在作业到期之前,我会从讲师那里弄清楚这是否是对模糊要求的正确解释。
答案 2 :(得分:1)
鉴于这是一个课程项目,我将提供一个不完整的答案,让您填补空白。
我对您要执行的操作的解释是提供一个实用程序
因此,首先,您需要确定两个lambda列表是否相同。例如,(x)
与(y)
一样,作为lambda列表吗?是的,它是:形式参数的名称仅在函数的实现中起作用,而您通常不知道它们:这两个lambda列表均表示“一个参数的函数”。
有趣的是各种可选参数:(a &optional b)
显然与(a)
不同,但是与(b &optional c)
相同,但是与{{1} }?在这段代码中,我说的是,这是相同的:默认值和可选参数的当前参数不会改变lambda列表是否相同。这是因为很多时候这些都是函数的实现细节。
我们将其放入包装中,以便清楚了解接口是什么
(a &optional (b 1 bp))
因此,首先,我们需要一种记录有关功能的信息的机制。我们将使用类似于(defpackage :com.stackoverflow.lisp.fdesc-search
(:use :cl)
(:export
#:defun/recorded
#:record-function-description
#:clear-recorded-functions
#:name->lambda-list
#:lambda-list->names))
(in-package :com.stackoverflow.lisp.fdesc-search)
但记录信息的宏来进行此操作,我将其称为defun
。我们希望能够甚至在程序存在之前就记录有关事物的信息,而我们可以通过将defun/recorded
存储在列表中的“待定”记录中,一旦程序存在,它将拉开并正确记录。这样我们就可以在整个代码中使用defun/recorded
。
defun/recorded
现在,我们希望能够匹配lambda列表。由于我们显然要将以lambda列表索引的内容存储在某种树中,因此我们只需要真正处理它们的匹配 elements 即可。而且(见上文)我们不在乎默认值之类的东西。我选择这样做的方法是,首先简化lambda列表以将其删除,然后匹配简化元素:还有其他方法。
;;; These define whether there is a recorder, and if not where pending
;;; records should be stashed
;;;
(defvar *function-description-recorder* nil)
(defvar *pending-function-records* '())
(defmacro defun/recorded (name lambda-list &body forms)
"Like DEFUN but record function information."
;; This deals with bootstrapping by, if there is not yet a recording
;; function, stashing pending records in *PENDING-FUNCTION-RECORDS*,
;; which gets replayed into the recorder at the point it becomes
;; available.
`(progn
;; do the DEFUN first, which ensures that the LAMBDA-LIST is OK
(defun ,name ,lambda-list ,@forms)
(if *function-description-recorder*
(progn
(dolist (p (reverse *pending-function-records*))
(funcall *function-description-recorder*
(car p) (cdr p)))
(setf *pending-function-records* '())
(funcall *function-description-recorder*
',name ',lambda-list))
(push (cons ',name ',lambda-list)
*pending-function-records*))
',name))
进行了简化,simplify-lambda-list
告诉您两个参数是否匹配:有趣的一点是,它需要知道必须完全匹配的lambda列表关键字,而其他所有内容都匹配。 argument-matches-p
常量由CL标准方便地提供。
lambda-list-keywords
关于功能的信息存储在称为(defun/recorded simplify-lambda-list (ll)
;; Simplify a lambda list by replacing optional arguments with inits
;; by their names. This does not validate the list
(loop for a in ll
collect (etypecase a
(symbol a)
(list (first a)))))
(defun/recorded argument-matches-p (argument prototype)
;; Does an argument match a prototype.
(unless (symbolp argument)
(error "argument ~S isn't a symbol" argument))
(unless (symbolp prototype)
(error "prototype ~S isn't a symbol" prototype))
(if (find-if (lambda (k)
(or (eq argument k) (eq prototype k)))
lambda-list-keywords)
(eq argument prototype)
t))
的对象中:这里没有给出这些对象的定义,但是我们需要回答的一个问题是“做两个fdesc
指的是同样的功能?好吧,如果函数的名称相同,它们也会这样做。请记住,函数名称不必是符号(允许使用fdesc
),因此我们必须与(defun (setf x) (...) ...)
而非equal
进行比较:
eql
(defun/recorded fdescs-equivalent-p (fd1 fd2)
;; do FD1 & FD2 refer to the same function?
(equal (fdesc-name fd1)
(fdesc-name fd2)))
为了通过lambda列表有效地索引事物,我们构建了一棵树。该树中的节点称为fdesc
,此处未给出其定义。
有些函数可以在树中实习lambda-list-tree-node
,并返回由给定的lambda列表索引的fdesc
列表。这里都没有实现,但这是他们的样子:
fdesc
这些功能的实现可能需要使用(defun/recorded intern-lambda-list (lambda-list tree-node fdesc)
;; return the node where it was interned
...)
(defun/recorded lambda-list-fdescs (lambda-list tree-node)
;; Return a list of fdescs for a lambda list & T if there were any
;; or NIL & NIL if there were not (I don't think () & T is possible,
;; but it might be in some future version)
...)
和argument-matches-p
。
现在,我们可以定义顶级数据库对象:用于通过lambda列表进行索引的树的根,以及用于通过名称进行索引的哈希表
fdescs-equivalent-p
请注意,(defvar *lambda-list-tree* (make-lambda-list-tree-node))
(defvar *tree-nodes-by-name* (make-hash-table :test #'equal))
从名称映射到存储有关该功能的信息的节点:这样做是为了简化重定义,如以下功能所示:
*tree-nodes-by-name*
请注意,此函数首先查找(defun/recorded record-function-description (name lambda-list)
"Record information about a function called NAME with lambda list LAMBDA-LIST.
Replace any existing information abot NAME. Return NAME."
(let ((fdesc (make-fdesc :name name :lambda-list lambda-list)))
;; First of all remove any existing information
(multiple-value-bind (node foundp) (gethash name *tree-nodes-by-name*)
(when foundp
(setf (lambda-list-tree-node-values node)
(delete fdesc (lambda-list-tree-node-values node)
:test #'fdescs-equivalent-p))))
(setf (gethash name *tree-nodes-by-name*)
(intern-lambda-list lambda-list *lambda-list-tree* fdesc)))
name)
的所有现有信息,如果存在,它将从找到该信息的节点中将其删除。这样可以确保函数重定义不会在树中留下过时的信息。
此功能是name
想知道的实际记录器,因此请告知:
defun/recorded
现在,下次我们调用(setf *function-description-recorder*
#'record-function-description)
时,它将通过插入所有未决的定义来引导系统。
defun/recorded
是该包API的一部分:可用于记录有关我们未定义的功能的信息。
除了record-function-description
和defun/recorded
之外,我们还需要一些函数使我们可以查询数据库,以及重置功能:
record-function-description
就是这样。
在编译,加载和使用包后(添加了丢失的位),我们首先可以向其中注入一些有用的额外功能(这只是随机分散)
(defun/recorded clear-recorded-functions ()
"Clear function description records. Return no values"
(setf *lambda-list-tree* (make-lambda-list-tree-node)
*tree-nodes-by-name* (make-hash-table :test #'equal))
(values))
(defun/recorded name->lambda-list (name)
"Look up a function by name.
Return either its lambda list & T if it is found, or NIL & NIL if not."
(multiple-value-bind (node foundp) (gethash name *tree-nodes-by-name*)
(if foundp
(values
(fdesc-lambda-list
(find-if (lambda (fd)
(equal (fdesc-name fd) name))
(lambda-list-tree-node-values node)))
t)
(values nil nil))))
(defun/recorded lambda-list->names (lambda-list)
"find function names matching a lambda-list.
Return a list of name & T if there are any, or NIL & NIL if none.
Note that lambda lists are matched so that argument names do not match, and arguments with default values or presentp parameters match just on the argument."
(multiple-value-bind (fdescs foundp) (lambda-list-fdescs lambda-list
*lambda-list-tree*)
(if foundp
(values (mapcar #'fdesc-name fdescs) t)
(values nil nil))))
现在我们可以进行一些查询:
> (dolist (x '(car cdr null))
(record-function-description x '(thing)))
nil
> (dolist (x '(car cdr))
(record-function-description `(setf ,x) '(new thing)))
nil
> (record-function-description 'cons '(car cdr))
cons
> (record-function-description 'list '(&rest args))
下面的一些代码演示了一种在树中存储信息的方法(通常称为tries)。 出于多种原因,以上无法使用,但阅读此书可能有助于实现缺少的部分。
> (lambda-list->names '(x))
(null cdr
car
lambda-list->names
name->lambda-list
com.stackoverflow.lisp.fdesc-search::simplify-lambda-list)
t
> (lambda-list->names '(&rest anything))
(list)
t
> (name->lambda-list 'cons)
(car cdr)
t