Здесь мы идем: частичный ответ/решение моего вопроса (почему частичное? См. Ниже). Большое спасибо sds за то, что помогли мне понять это!
Позвольте мне начать разъяснение. Когда я задал свой вопрос изначально, я использовал термин «параметрический тип» неточно, имея в виду лишь неопределенное определение как «зависящие от типа параметры». Я в основном хотел немного гаджет позволяет мне написать следующий псевдокод (в псевдо-языке):
class List<T> {
//implementation
};
l = new List<string>;
l.push("Hello World!");
Осмысления вышеприведенного псевдокода является довольно прямо вперед (см ответа ИКБА). Однако возникает двусмысленность, если вы начинаете задумываться о том, должны ли значения выражения List<T>
и List
иметь значение. Действительно, в C++, например, выражения будут неопределенными, для эффекта определения шаблона
template <typename T>
class List {
public:
T car;
List<T> *cdr;
};
как бы определение отдельно для каждого типа T
, типа List<T>
. В отличие от этого, в таких языках, как Java, которые реализуют общие типы, выражение List<T>
(где T
является свободной переменной) имеет смысл и обозначает тип, а именно общий тип списка над некоторых типа T
, так что можно было бы например написать полиморфный функцию как
T car(List<T> l) {
return l.car;
}
Короче говоря, в C++ у нас есть только (бесконечная) совокупность всех типов List<T>
где T
пробегает всех типов, в то время как в Java сама эта коллекция существует как объект, на языке, как общий тип List<T>
.
Сейчас на моем предлагаемом частичном решении, который я кратко набросаю на словах перед написанием фактического кода Lisp. Решение основано на типах и таких зависимых сумм, то есть мы будем интерпретировать параметрический тип типа List<T>
выше как функцию List
одного аргумента, значения которого являются типами, и мы подделываем Java -стильный общий тип List<T>
как тип зависимой суммыDepSum(List)
, который просто состоит из пар (a,b)
где a
- это какой-то тип и b
имеет тип List(b)
.
Возвращаясь к примеру определения вещественного векторного пространства над множеством X
, я хотел бы написать что-то вроде
(defclassfamily RealVectorSpaceOver (X)()
((add :initarg :add
:reader add
:type (function (X X) X))
(s-mult :initarg :s-mult
:reader s-mult
:type (function (real X) X)))
определяющим для меня функцию RealVectorSpaceOver, что, учитывая класс A
, будет возвращать класс как если определено вручную
(defclass RealVectorSpaceOverA()
((add :initarg :add
:reader add
:type (function (A A) A))
(s-mult :initarg :s-mult
:reader s-mult
:type (function (real A) A)))
в принципе, я мог бы скопировать-н-паста решение ИКБ здесь, но есть две проблемы. Прежде всего, результат не будет функцией (побочный эффект бесплатно), например, форма
(typep (make-instance (RealVectorSpaceOver A)
:add (lambda (x y) nil)
:s-mult (lambda (x y) nil))
(RealVectorSpaceOver A))
будет вычисляться nil
, потому что есть два вызова RealVectorSpaceOver
здесь, и каждый вызов создает новый (и поэтому другой) класс. Таким образом, нам нужно обернуть эту функцию в некоторый код, который кэширует результат, как только он был вызван в первый раз.
Другая проблема заключается в том, что при использовании defclass
для создания классов программным путем происходит изменение пространства имен классов, возможно, переопределение существующих классов. Чтобы этого избежать, можно вместо этого создавать новые классы, создавая экземпляр метакласса standard-class
. Например
(make-instance 'standard-class
:name (intern "B")
:direct-superclasses '(A)
:direct-slots '((x :initargs (:x) :readers (x))))
эквивалентно
(defclass B (A)
((x :initarg :x :reader x)))
но не переопределить любой уже существующий класс B
. Обратите внимание, что формат аргумента :direct-slots
немного отличается от формата defclass
для определения слотов. Используя вспомогательную функцию canonicalize-direct-slot-defs
, превращающий последний в бывший (взято из книги «Искусство в метаобъект протокола»), макрос defclassfamily
может быть реализован следующим образом:
(defmacro defclassfamily (name variables superclasses slot-defs)
(let ((stripped-variables (strip-variables variables))
(variable-types (types-of-variables variables))
(type-decls (type-decls-from-variables variables)))
`(flet ((f ,stripped-variables
(make-instance 'standard-class
:name (intern (format nil "~S<~S>" ',name (list ,@stripped-variables)))
:direct-superclasses ,superclasses
:direct-slots ,(canonicalize-direct-slots slot-defs))))
(let ((g (cache-function #'f)))
(defun ,name ,stripped-variables
,@type-decls
(the standard-class (funcall g ,@stripped-variables)))
(defmethod argument-signature ((x (eql #',name)))
',variable-types)))))
Приведенный выше код первого определяет функцию f
, представляющий семейство желаемого типа, затем создает кэшированную версию g
с помощью вспомогательной функции cache-function
(вставьте свою собственную реализацию), а затем определяет новую функцию в пространстве имен с помощью defun
, применяя типы аргументов (defclassfamily
принимает набранные аргументы, подобные defmethod
, так что (defclassfamily F ((X Set) Y) ...
определит семейство F
of t wo, причем первый из них имеет тип Set
) и возвращаемое значение семейства классов. Кроме того, существуют некоторая простая вспомогательная функция strip-variables
, types-of-variables
и type-decls-from-variables
, которые преобразуют выражение, заданное переменными типа семейства ((X Set) Y
в предыдущем примере). Они определяются следующим образом:
(defun strip-variables (specialized-lambda-list)
(mapcar (lambda (x)
(if (consp x)
(car x)
x))
specialized-lambda-list))
(defun types-of-variables (var-declarations)
(mapcar (lambda (var-declaration) (if (consp var-declaration) (second var-declaration) t)) var-declarations))
(defun type-decls-from-variables (var-declarations)
(mapcar (lambda (var-declaration)
(if (consp var-declaration)
`(declare (type ,(second var-declaration) ,(first var-declaration)))
`(declare (type t ,var-declaration))))
var-declarations))
Наконец, мы записываем тип аргументов, принимаемых нашей семьей с использованием методы argument-signature
, так что
(argument-signature (defclassfamily F ((X Set) Y) ...))
будет вычисляться (Set t)
.
Зависимая сумма типа семьи одного параметра реализуется следующим кодом:
(defclass DepSum (standard-class)
((family :initarg :family :reader family)
(arg-type :initarg :arg-type :reader arg-type)))
(defmethod make-instance :before ((sum-class DepSum) &key pr1 pr2)
(assert (and (typep pr1 (arg-type sum-class))
(typep pr2 (funcall (family sum-class) pr1)))))
(defmethod sb-mop:validate-superclass ((class DepSum) (super-class standard-class))
t)
(defun depsum (f)
(let ((arg-type (car (argument-signature f))))
(make-instance 'DepSum
:name (intern (format nil "DepSum_{x:~A} ~A(x)" arg-type f))
:direct-superclasses()
:direct-slots `((:name pr1 :initargs (:pr1) :readers (pr1) :type ,arg-type)
(:name pr2 :initargs (:pr2) :readers (pr2)))
:direct-slots `((:name pr1 :initargs (:pr1) :readers (pr1)))
:family f
:arg-type arg-type)))
так, чтобы мы могли определить тип RealVectorSpace
с помощью
(let ((rvs-type (depsum #'RealVectorSpaceOver)))
(deftype RealVectorSpace()
rvs-type))
и написать
(make-instance (depsum #'RealVectorSpaceOver) :pr1 X :pr2 some-rvs-over-X)
для создания объект типа RealVectorSpace
. Вышеупомянутый код работает путем создания метакласса (т. Е. Подкласса standard-class
) DepSum
, который представляет тип всех зависимых сумм и экземпляры которых зависят от сумм конкретных семейств. Тип безопасности обеспечивается путем подключения к вызовам типа (make-instance (depsum #'RealVectorSpaceOver) ...)
через (defmethod make-instance :before ((sum-class DepSum ...
. К сожалению, нам кажется, что мы должны полагаться на assert
для проверки этого типа (я не смог выяснить, как заставить его работать с declare
). Наконец, код (defmethod sb-mop:validate-superclass ...
зависит от реализации (для SBCL в этом случае) и необходим для создания экземпляров DepSum
, таких как (depsum #'RealVectorSpaceOver)
.
Почему это только частичный ответ? Потому что я не сделал аксиомы векторного пространства частью типа RealVectorSpaceOver
(или RealVectorSpace
). Действительно, такая вещь потребует, чтобы дать реальное доказательство этих аксиом как часть опорной точки в вызове (make-instance (RealVectorSpaceOver X) ...
. Такая вещь, безусловно, возможна в таких причудливых языках, как Coq, но кажется совершенно недосягаемой в этом старом, но привлекательном беспорядке, который является Common Lisp.
Поскольку Common Lisp не имеет полезной системы типов для этого и особо зависимых типов, это имеет мало смысла. Я предлагаю использовать язык программирования с сложной системой типа Haskell или что-то вроде Axiom (http://fricas.github.io/api/VectorSpaceBasis.html). –
Конечно, у Common Lisp нет полезной системы типов. Но вопрос в том, что означает «имеет»? Например, даже без CLOS вы можете легко приготовить свое собственное OO только через лексические закрытия. – BlenderBender
если у вас есть конкретный вопрос, лучше всего с кодом, тогда спросите. В противном случае я опасаюсь, что широкий спекулятивный вопрос не подходит для Stackoverflow, который фокусируется на реальных практических проблемах программирования. –