Как работает интеллектуальное завершение кода в Scheme?

Из чтения книги по Лиспу я помню, что они показали пример диспетчера методов в стиле ООП, основанного на замыканиях:

(defun create-object ()
  (let ((val 0)
        (get (lambda () val))
        (set (lambda (new-val) (setq val new-val)))
        (inc (lambda () (setq val (+ 1 val)))))
    (lambda (method)
      (cond ((eq method 'get)
             get)
            ((eq method 'set)
             set)
            ((eq method 'inc)
             inc)))))

(let ((obj (create-object)))
  (funcall (obj 'set) 1)
  (funcall (obj 'inc))
  (funcall (obj 'get))) ;; 2

Поскольку это всего лишь функция с аргументом-символом string, я думаю, что информация о коде здесь не поможет, не заполняя имена методов или их подписи. (Сравните с аналогичным объектом JavaScript.)

Эта проблема вообще решается? Как вы программируете объектную систему в Scheme, чтобы редактор (например, Emacs) мог работать с вашим кодом более интеллектуально?

P.S. Пример может быть недействительным кодом Схемы, но вы должны уловить идею.


person katspaugh    schedule 19.11.2013    source источник
comment
у вас может быть специальная функция, как в python __dir__, которая будет возвращать все возможные ключи, которые можно использовать для объекта.   -  person Loïc Faure-Lacroix    schedule 19.11.2013
comment
То, что ООП может быть реализовано таким образом, не означает, что обычно можно. Но даже здесь использование eq в строках, как правило, не является хорошей идеей, поэтому было бы более распространенным сравнение символов (например, (eq method 'get)) и выполнение завершения на основе символов, которые видел читатель.   -  person Joshua Taylor    schedule 19.11.2013


Ответы (3)


Я сделал для вас стартовый код. Это для Emacs Lisp, но его должно быть очень легко перенести на Scheme.

Вот ваш пример использования:

(defun create-object ()
  (lexical-let* ((val 0)
                 (get (lambda() val))
                 (set (lambda(x) (setq val x))))
    (generate-dispatch-table get set)))

(setq obj (create-object))
(funcall (funcall obj 'get))
;; => 0
(funcall (funcall obj 'set) 1)
;; => 1
(funcall (funcall obj 'get))
;; => 1
(scheme-completions obj)
;; => (get set)

И вот как это реализовано:

(defmacro generate-dispatch-table (&rest members)
  `(lambda (method)
     (cond ,@(mapcar
               (lambda (x) `((eq method ',x) ,x)) members))))

(defun collect (pred x)
  (when (and x (listp x))
    (let ((y (funcall pred x))
          (z (append
              (collect pred (car x))
              (collect pred (cdr x)))))
      (if y
          (append (list y) z)
        z)))) 

(defun scheme-completions (obj)
  (collect
   (lambda(x) (and (eq (car x) 'eq)
              (eq (cadr x) 'method)
              (eq (caaddr x) 'quote)
              (cadr (caddr x))))
   obj))

А вот и простой визуальный интерфейс для доработок:

(require 'helm)
(defun scheme-completions-helm ()
  (interactive)
  (let ((y (and
               (looking-back "(funcall \\([^ ]*\\) +")
               (intern-soft (match-string 1)))))
    (when y
      (helm :sources
            `((name . "members")
              (candidates . ,(scheme-completions (eval y)))
              (action . (lambda(x) (insert "'" x)))))))) 
person abo-abo    schedule 29.11.2013
comment
Я принимаю этот ответ, потому что он дает конкретный пример того, как система объектов может информировать редактора о системе. Если подытожить, то 1) объект должен иметь отдельную подпись для доступа к членам и 2) интерфейс для перечисления всех его членов. - person katspaugh; 06.12.2013

Я не пользователь Emacs, но использую DrRacket, и у него есть объектная система, и он делает то, что должна делать IDE, но я знаю, что Emacs очень настраиваемый, поскольку он использует elisp, поэтому вы можете поддерживать свой собственный синтаксис как при подсветке синтаксиса. и завершение табуляции. Итак, вы делаете:

  1. Создайте свою собственную систему объектов
  2. Отредактируйте свой редактор Emacs, чтобы делать то, что вы хотите

Многие мои коллеги используют его и таким образом исправляют свой Emacs.

Еще один момент: этот вопрос заставляет меня задуматься об ресурсах на Schemewiki.org по этому вопросу. где упоминаются различные подходы, и даже в качестве примера публикуется код, аналогичный тому, который вы опубликовали. Это хорошее чтение.

person Sylwester    schedule 19.11.2013
comment
Спасибо за ссылку, действительно интересно! В частности, страница о продвинутых объектных системах. - person katspaugh; 29.11.2013

Я бы избегал двойного представления символов в create-object через obarray. Кроме того, интерфейс объекта — это все функции. Поэтому используйте fset и избегайте двойного funcall.

(defun create-object ()
  (lexical-let (val
        (_oa (make-vector 11 0)))
    (fset (intern "get" _oa) (lambda () val))
    (fset (intern "inc" _oa) (lambda () (incf val)))
    (fset (intern "set" _oa) (lambda (new-val) (setq val new-val)))
    (lambda (method &rest args)
      (apply 'funcall (intern (symbol-name method) _oa) args))))


(fset 'obj1 (create-object))
(fset 'obj2 (create-object))

(obj1 'set 1)
(obj2 'set 2)

(obj1 'inc)
(obj2 'inc)
(obj2 'inc)

(obj2 'get)
(obj1 'get)

Пример для наследования:

(defun create-object ()
  (lexical-let (val
        (_oa (make-vector 11 0)))
    (fset (intern "get" _oa) (lambda () val))
    (fset (intern "inc" _oa) (lambda () (incf val)))
    (fset (intern "set" _oa) (lambda (new-val) (setq val new-val)))
    (lambda (method &rest args)
      (apply 'funcall (or (intern-soft (symbol-name method) _oa)
              (error "Undefined function: %s" method))
         args))))


(defun create-object-add10 ()
  (lexical-let ((base (create-object))
        (_oa (make-vector 11 0)))
    (fset (intern "inc" _oa) (lambda () (funcall base 'set (+ (funcall base 'get) 10))))
    (lambda (method &rest args)
      (let ((call (intern-soft (symbol-name method) _oa)))
    (if call
        (apply 'funcall call args)
      (apply 'funcall base method args))))))

(fset 'obj1 (create-object))
(fset 'obj2 (create-object-add10))

(obj1 'set 1)
(obj2 'set 2)

(obj1 'inc)
(obj2 'inc)
(obj2 'inc)

(obj2 'get)
(obj1 'get)

Определение create-object-подобных методов должно дополнительно поддерживаться через макросы. Здесь этого не делают.

Для получения дополнительных возможностей обратите внимание, что в emacs есть CLOS-совместимая объектно-ориентированная система:

https://www.gnu.org/software/emacs/manual/html_node/eieio/index.html

person Tobias    schedule 30.11.2013
comment
Спасибо, приятно! Я вижу, как obarray можно уподобить объекту JavaScript и упростить запись функции завершения (например, в ответе @abo-abo). - person katspaugh; 30.11.2013
comment
Да, и obarray изменяет доступ к линейному времени на доступ к постоянному времени. Он приближается к методу внутреннего поиска. Это может иметь значение для крупных объектов. - person Tobias; 30.11.2013
comment
Я думаю, что замыкания — не лучший способ реализации объектов. Списки obarray лучше, так как они допускают наследование. - person Tobias; 30.11.2013
comment
Вы имеете в виду, что массив данных (в примере val) должен быть в том же списке, что и методы obarray? - person katspaugh; 01.12.2013
comment
Я должен извиниться. Я думал о представлении классов в виде списков obarray. Но это оказалось громоздко, потому что copy-tree и copy-sequence не копируют векторы, поэтому объекты будут совместно использовать данные, которые просто неверны, или придется писать свою собственную копию. В качестве компенсации я добавил пример наследования со старой концепцией. Функция inc перезаписывается в производном классе, созданном с помощью create-object-add10. На самом деле, я действительно не хотел писать этот код, потому что в ссылке есть много примеров из ответа Сильвестра. - person Tobias; 01.12.2013