Понимание общих функций в Common Lisp?

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

Я перепечатаю пример здесь:


(defclass human () ())
(defclass dog () ())

(defmethod greet ((thing human))
  (print "Hi human!"))

(defmethod greet ((thing dog))
  (print "Wolf-wolf dog!"))

(defparameter Anna (make-instance 'human))
(defparameter Rex (make-instance 'dog))

(greet Anna) ;; => "Hi human"
(greet Rex)  ;; => "Wolf-wolf dog!"

Мой вопрос, используя тот же пример:

  1. Какую ценность добавило бы создание универсальных функций?
  2. Чем полезны универсальные функции? Похожи ли они на экземпляры в других объектно-ориентированных языках, которые обеспечивают структуру?

Кажется, что универсальные функции создаются в фоновом режиме неявно (не уверен на 100%). Я заметил, что когда я играю с этим примером, если я создаю метод с другой структурой параметров, чем у первого экземпляра метода, я получаю generic function error.

cl-cookbook.sourceforge.net/clos-tutorial
Rainer Joswig 17.11.2022 08:57
algo.be/cl/documents/clos-guide.html
Rainer Joswig 17.11.2022 08:58
Что такое компоненты React? Введение в компоненты | Типы компонентов
Что такое компоненты React? Введение в компоненты | Типы компонентов
Компонент - это независимый, многократно используемый фрагмент кода, который делит пользовательский интерфейс на более мелкие части. Например, если мы...
2
2
131
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

DEFMETHOD создает общую функцию, если ее не существует.

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

Также возможно указать общую функцию и использовать ее также для указания методов - в одной форме `DEFGENERIC. Однако это не так уж часто.

Существуют правила, которые должны использовать списки параметров методов. Например, все списки параметров методов определенных универсальных функций должны иметь одинаковое количество обязательных параметров.

Общие функции и их методы являются базовой конструкцией объектно-ориентированного поведения в CLOS. Вместо виртуальных методов или сообщений CLOS использует функции, которые лучше подходят для Lisp. Таким образом, общие функции CLOS также могут передаваться и возвращаться из других функций.

Универсальные функции также немного необычны, поскольку универсальная функция и методы сами являются CLOS-объектами. Обобщенная функция является одновременно объектом FUNCTION и CLOS.

Это также означает, что мне не нужно явно определять общую функцию. Я могу просто создать метод и класс на основе моих идей реализации?

Vinn 17.11.2022 09:13
Ответ принят как подходящий

Какую ценность добавило бы создание универсальных функций?

Мне нравится явно объявлять универсальную функцию, потому что можно добавить документацию и объявления, относящиеся к универсальной функции (оптимизировать для скорости/пространства/отладки), и другие детали, такие как комбинация методов (например, когда у вас есть несколько методов, применимых для данный вызов, это определяет, какие выполняются и в каком порядке). Здесь, например, я могу определить talk как комбинацию методов progn (все методы выполняются так, как если бы они были инкапсулированы в форму progn):

(defgeneric talk (subject)
  (:documentation "Say something to standard output")
  (:method-combination progn))

Это немного надуманный пример, но начнем:

(defclass human () ())
(defmethod talk progn ((a human))
  (print "hello"))

(defclass wolf () ())
(defmethod talk progn ((a wolf))
  (print "owooooo!"))

(defclass werewolf (human wolf) ())

Определение класса, который наследуется от обоих, означает, что вызов talk для экземпляра класса может выполнять два метода (отсортированных в некотором топологическом порядке, называемом порядком разрешения методов). Таким образом, с этой комбинацией методов выполняются все методы:

* (talk (make-instance 'werewolf))
"hello" 
"owooooo!"

Но я бы сказал, что возможность задокументировать универсальную функцию сама по себе является достаточной причиной, чтобы объявить ее с помощью defgeneric.

Чем полезны универсальные функции?

Если вы определяете talk как общую функцию, вы разрешаете любому классу участвовать в коде, который вызывает talk (например, библиотеку), это способ разрешить расширения без необходимости закрывать набор возможных значений, в отличие от использования что-то вроде typecase в функции где вы можете перечислить только предопределенный набор случаев.

Например, стандартные функции print-object вызываются в разное время в вашей реализации Lisp (в инспекторе, REPL, отладчике), если вы хотите, вы можете реализовать метод для ваших пользовательских типов, не взламывая внутренности вашей среды.

Похожи ли они на экземпляры в других объектно-ориентированных языках, которые обеспечивают структуру?

Общие функции, в отличие от других ОО-языков, не привязаны к одному классу или экземпляру. Они могут быть специализированы более чем по одному аргументу, что означает, что ни один из них не «владеет» универсальной функцией.


1. специализированный определяется следующим образом:

специализируются в.т. (общая функция) для определения метода универсальной функции или, другими словами, для уточнения поведения универсальной функции путем придания ей определенного значения для определенного набора классов или аргументов.

Идея заключается в том, что методы могут быть более или менее конкретными: метод с двумя аргументами a и b, который специализируется как на (a number), так и на (b string), более специфичен, чем другой, который специализируется только на (b vector), и методы фактически сортируются от наиболее конкретных к наименее конкретным (соответственно от наименее к наиболее) при их объединении. Вы даже можете специализировать функцию на (a (eql 10)), чтобы покрыть только конкретный случай, когда аргумент a равен eql до 10.

Я постоянно вижу это слово specialized. Но что это означает в контексте CLOS, в частности?

Vinn 17.11.2022 18:06

Есть несколько причин явно определять универсальные функции вместо того, чтобы позволять defmethod делать это неявно.

Один из них применим всегда, потому что вы хотите писать хорошие программы, а в хороших программах есть такие вещи, как документация для функций, которые составляют их важные части. С помощью CLOS это можно сделать с помощью defgeneric: defgeneric — это место, где вы можете поместить большую строку документации или комментарий, который сообщает людям, что делает универсальная функция, почему может потребоваться написать методы, какие могут быть ограничения на эти методы. («не пишите для этого методы, которые специализируются на классах, определенных CL») и так далее.

Еще один способ улучшить программы с помощью явных универсальных функций — использовать определение универсальной функции, чтобы явно указать, какие аргументы должны быть у методов:

(defgeneric expunge-bogon (bogon &key with-force))

заявляет, что все методы в expunge-bogon должны поддерживать аргумент ключевого слова with-force (они также могут поддерживать другие аргументы ключевого слова, но они должны поддерживать это).

Кроме того, есть много вещей, которые вы можете сделать, только указав общую функцию, например, указав комбинацию методов, которая не является комбинацией по умолчанию. В качестве небольшого примера, если вы используете комбинацию методов Tim Bradshaw'w wrapping-standard , чтобы убедиться, что вы можете обернуть метод вокруг любых других методов, то вы можете (используя другую его утилиту, destructuring-match в способ, которым он, вероятно, не предназначался для использования) убедитесь, что разрешены только аргументы ключевого слова, которые вы указываете в универсальной функции (к сожалению, только во время выполнения):

(defgeneric expunge-bogon (bogon &key with-force)
  (:method-combination wrapping-standard)
  (:method :wrapping (bogon &rest args)
   (destructuring-match args
     ((&key ((:with-force _)))
      (call-next-method))
     (otherwise
      (error "extra keyword arguments in ~S" args)))))

Теперь дано

(defclass entitled-billionaire ()
  ())

(defmethod expunge-bogon ((bogon entitled-billionaire)
                         &key (with-force t) (also-burn t))
  ...)

затем

> (expunge-bogon (make-instance 'entitled-billionaire) :also-burn nil)

Error: extra keyword arguments in (:also-burn nil)

Другие вопросы по теме