2

对于一个课程项目,我必须用 lisp 编写一个程序。

该程序应该包含最重要的 lisp 函数,它们的输入和输出参数,可能还有可选参数。

例如:函数 - 第一,输入 - 列表,输出 - 对象(列表的第一个成员)。

该程序应该以两种不同的方式工作:

  1. 你给程序一个函数的名字,它应该返回函数参数。

  2. 您输入函数参数,如果具有这些参数的函数存在,它应该返回函数的名称。

我的问题:

  1. 在 lisp 中处理这样的任务的正确方法是什么?我想也许一棵树是一种处理它的方法?(制作一个包含所有函数和参数的树,然后编写一个处理它的程序)。

  2. 有没有人有比这更好的想法来完成这项任务?或者一些建议从哪里/如何开始?或包含任何信息的教程?

目前我有点迷茫如何开始。您可以提供的任何帮助将不胜感激。

英语不是我的第一语言,所以我希望一切都可以理解。

问候。

4

3 回答 3

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)

此函数以列表的形式返回所有匹配的函数。不止一个函数可以有这个签名。

诀窍是找到可选参数和剩余参数的良好表示。它可能与该语言使用的符号相同:

(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)

在作业到期之前,我会从导师那里得到澄清,这是否是对模糊要求的正确解释。

于 2019-03-15T00:00:37.983 回答
2

首先看一下准备好你常用的lisp开发环境。在那之后,我认为你应该调查:

和类似的事情。接下来看看两个常见的 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)))
"
于 2019-03-14T11:03:55.167 回答
1

鉴于这是一个课程项目,我将提供一个不完整的答案,并让您填写空白。

程序应该做什么

我对您被要求做的事情的解释是提供一个实用程序,它将

  • 给定函数的名称,返回其参数列表(下面称为“lambda 列表”);
  • 给定一个 lambda 列表,返回具有该 lambda 列表的所有函数。

因此,首先您需要确定两个 lambda 列表是否相同。作为一个例子是(x)一样的(y),作为一个 lambda 列表?是的,它是:形式参数的名称仅在函数的实现中很重要,而且您通常不会知道它们:这两个 lambda 列表都表示“一个参数的函数”。

有趣的是各种可选参数:(a &optional b)显然与 不同(a),但与 相同,(b &optional c)但是否与 相同(a &optional (b 1 bp))?在这段代码中,我说是的,它是相同的:可选参数的默认值和当前参数不会改变 lambda 列表是否相同。这是因为这些通常是函数的实现细节。

一袋

我们将它放入一个包中,以便清楚接口是什么:

(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在整个代码中使用。

;;; 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))

匹配 lambda 列表,第一步

现在我们希望能够匹配 lambda 列表。由于我们显然要将由 lambda 列表索引的东西存储在某种树中,我们只需要能够处理它们的匹配元素。而且(见上文)我们不关心默认值之类的东西。我选择首先简化 lambda 列表以删除它们,然后匹配 simplifies 元素来做到这一点:还有其他方法。

simplify-lambda-list进行简化并argument-matches-p告诉您两个参数是否匹配:有趣的是它需要了解 lambda 列表关键字,这些关键字必须完全匹配,而其他所有内容都匹配任何内容。该lambda-list-keywords常数由 CL 标准方便地提供。

(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))

功能说明(部分)

有关函数的信息存储在名为fdescs 的对象中:这里没有给出这些对象的定义,但我们需要回答的一个问题是“两个fdescs 是否指代同一函数的版本?” 好吧,如果函数的名称相同,它们就会这样做。请记住,函数名称不必是符号((defun (setf x) (...) ...)允许),因此我们必须与equalnot进行比较eql

(defun/recorded fdescs-equivalent-p (fd1 fd2)
  ;; do FD1 & FD2 refer to the same function?
  (equal (fdesc-name fd1)
         (fdesc-name fd2)))

存储fdesc由 lambda 列表索引的 s(部分)

为了通过 lambda 列表有效地索引事物,我们构建了一棵树。这棵树中的节点称为lambda-list-tree-nodes,这里不给出它们的定义。

有一些函数fdesc在树中实习 a,并返回fdesc由给定 lambda 列表索引的 s 列表。这里都没有实现,但这就是它们的样子:

(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)
  ...)

这些功能的实现可能需要使用 useargument-matches-pfdescs-equivalent-p.

顶级数据库(略部分)

现在我们可以定义顶级数据库对象:用于通过 lambda 列表进行索引的树的根,以及用于通过名称进行索引的哈希表

(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它时,将通过插入所有未决的定义来引导系统。

record-function-description是包 API 的一部分:它可用于记录有关我们未定义的函数的信息。

用户界面功能

除了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))

现在我们可以进行一些查询:

 > (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

将东西存储在树中的示例

下面是一些代码,演示了一种在树中存储信息的方法(通常称为尝试)。 由于很多原因,这在上面不可用,但阅读它可能有助于实现缺失的部分。

;;;; Storing things in trees of nodes
;;;

;;; Node protocol
;;;
;;; Nodes have values which may or may not be bound, and which may be
;;; assigned.  Things may be interned in (trees of) nodes with a
;;; value, and the value associated with a thing may be retrieved
;;; along with an indicator as to whether it is present in the tree
;;; under the root.
;;;

(defgeneric node-value (node)
  ;; the immediate value of a node
  )

(defgeneric (setf node-value) (new node)
  ;; Set the immediate value of a node
  )

(defgeneric node-value-boundp (node)
  ;; Is a node's value bound?
  )

(defgeneric intern-thing (root thing value)
  ;; intern a thing in a root, returning the value
  (:method :around (root thing value)
   ;; Lazy: this arround method just makes sure that primary methods
   ;; don't need to beother returning the value
   (call-next-method)
   value))

(defgeneric thing-value (root thing)
  ;; return two values: the value of THING in ROOT and T if is it present, or
  ;; NIL & NIL if not
  )


;;; Implementatation for STRING-TRIE-NODEs, which store strings
;;;
;;; The performance of these will be bad if large numbers of strings
;;; with characters from a large alphabet are stored: how might you
;;; fix this without making the nodes enormous?
;;;

(defclass string-trie-node ()
  ;; a node in a string trie.  This is conceptually some kind of
  ;; special case of an abstract 'node' class, but that doesn't
  ;; actually exist.
  ((children-map :accessor string-trie-node-children-map
                 :initform '())
   (value :accessor node-value)))

(defmethod node-value-boundp ((node string-trie-node))
  (slot-boundp node 'value))

(defmethod intern-thing ((root string-trie-node) (thing string) value)
  ;; intern a string into a STRING-TRIE-NODE, storing VALUE
  (let ((pmax (length thing)))
    (labels ((intern-loop (node p)
               (if (= p pmax)
                   (setf (node-value node) value)
                 (let ((next-maybe (assoc (char thing p) 
                                          (string-trie-node-children-map node)
                                          :test #'char=)))
                   (if next-maybe
                       (intern-loop (cdr next-maybe) (1+ p))
                     (let ((next (cons (char thing p)
                                       (make-instance (class-of node)))))
                       (push next (string-trie-node-children-map node))
                       (intern-loop (cdr next) (1+ p))))))))
      (intern-loop root 0))))

(defmethod thing-value ((root string-trie-node) (thing string))
  ;; Return the value associated with a string in a node & T or NIL &
  ;; NIL if there is no value for this string
  (let ((pmax (length thing)))
    (labels ((value-loop (node p)
               (if (= p pmax)
                   (if (node-value-boundp node)
                       (values (node-value node) t)
                     (values nil nil))
                 (let ((next (assoc (char thing p)
                                    (string-trie-node-children-map node)
                                    :test #'char=)))
                   (if next
                       (value-loop (cdr next) (1+ p))
                     (values nil nil))))))
      (value-loop root 0))))


;;; Draw node trees in LW
;;;

#+LispWorks
(defgeneric graph-node-tree (node))
  (:method ((node string-trie-node))
   (capi:contain
    (make-instance 'capi:graph-pane
                   :roots `((nil . ,node))
                   :children-function (lambda (e)
                                        (string-trie-node-children-map (cdr e)))
                   :edge-pane-function (lambda (pane parent child)
                                         (declare (ignore pane parent))
                                         (make-instance
                                          'capi:labelled-line-pinboard-object
                                          :text (format nil "~A" (car child))))
                   :print-function (lambda (n)
                                     (let ((node (cdr n)))
                                       (format nil "~A"
                                               (if (node-value-boundp node)
                                                   (node-value node)
                                                 ""))))))))
于 2019-03-17T15:52:25.313 回答