LISP - программа для поиска конкретной функции по ее параметрам - PullRequest
2 голосов
/ 14 марта 2019

Для курсового проекта мне нужно написать программу на lisp.

Программа должна содержать наиболее важные функции lisp, их входные и выходные параметры и, возможно, дополнительные параметры.

Например: function - first, input - list, output - object (первый член списка).

Программа должна работать двумя различными способами:

  1. Вы даете программе имя функции, и она должна возвращать параметры функции.

  2. Вы вводите параметры функции, и если функция с этими параметрами существует, она должна вернутьназвание функции.

Мои вопросы:

  1. Как правильно подходить к такой задаче в lisp?Я думаю, может быть, дерево будет способ справиться с этим?(создайте дерево со всеми функциями и параметрами, а затем напишите программу, которая его обрабатывает).

  2. У кого-нибудь есть идея лучше, чем эта, для решения этой задачи?Или какие-то предложения, где / как начать?Или учебники, содержащие какую-либо информацию?

В данный момент я немного растерялся, как начать.Любая помощь, которую вы можете оказать, будет высоко оценена.

Английский не мой родной язык, поэтому я надеюсь, что все понятно.

Привет.

Ответы [ 3 ]

2 голосов
/ 15 марта 2019

На первый взгляд, задача состоит в создании простой символической базы данных в памяти, доступной для поиска двумя способами.Записи в базе данных считаются функциями.«Выходные параметры», вероятно, можно понимать как одно или несколько возвращаемых значений.Эти вещи не названы в 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)

Я бы получил разъяснение от инструктора, является ли этоявляется правильным толкованием расплывчатых требований до того, как будет назначено задание.

2 голосов
/ 14 марта 2019

Прежде всего, взгляните, чтобы подготовить вашу общую среду разработки для 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

И, наконец, последний совет по работе со строками из вывода описаний:

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 голос
/ 17 марта 2019

Учитывая, что это учебный проект, я собираюсь дать неполный ответ и оставлю вас заполнять пробелы.

Что должна делать программа

Моя интерпретация того, что вас просят сделать, - предоставить утилиту, которая будет

  • с учетом имени функции, возвращающей список аргументов (называемый ниже «лямбда-списком»);
  • при наличии лямбда-списка возвращаются все функции с этим лямбда-списком.

Итак, прежде всего вам необходимо решить, являются ли два лямбда-списка одинаковыми или нет. Например, (x) совпадает с (y), как лямбда-лист? Да, это так: имена формальных параметров имеют значение только при реализации функции, и вы, как правило, не будете их знать: оба этих лямбда-списка означают «функцию одного аргумента».

Интересующая вещь - необязательные аргументы различного рода: (a &optional b) явно не то же самое, что (a), но то же самое, что (b &optional c), но это то же самое, что (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 во всем этом коде.

;;; 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 выполняет упрощение, а argument-matches-p сообщает вам, совпадают ли два аргумента: интересный момент заключается в том, что он должен знать о ключевых словах лямбда-списка, которые должны точно совпадать, а все остальное совпадает с чем-либо. Константа 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))

Описание функций (частичное)

Информация о функциях хранится в объектах, называемых fdesc s: определение этих объектов здесь не дается, но нам нужно ответить на один вопрос: «два fdesc s ссылаются на версии одной и той же функции?» Ну, они делают, если имена функций совпадают. Помните, что имена функций не обязательно должны быть символами (допускается (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)))

Хранение fdesc, проиндексированных лямбда-списком (частично)

Чтобы эффективно индексировать объекты с помощью лямбда-списка, мы строим дерево. Узлы в этом дереве называются lambda-list-tree-node s, и их определение здесь не приводится.

Существуют функции, которые интернируют 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 и 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, он загрузит систему, вставив все ожидающие определения.

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