我想写一些处理反射组的通用代码,因此需要设置一些反映数学结构的类型(向量空间,仿射空间......)。由于我真的想在类型中忠实地反映这些结构,我需要一种方法来定义某种参数类型。
所以特别是,我希望能够编写以下代码
(defclass RealVectorSpace ()
((V :accessor underlying-set
:type Set)
(vector-add :accessor add
:type (function ((set-as-type V) (set-as-type V)) (set-as-type V)))
(scalar-mult :accessor s-mult
:type (function (real (set-as-type V)) (set-as-type V)))
应该指定一个新的类型RealVectorSpace,它将由三个(V矢量添加标量)给出,其中V可以是任何东西,而vector-add是一个函数,它采用V(sic)的两个参数来评估某个东西类型V。
当然,这种类型并不是对真实向量空间概念的忠实反映,因为向量加法和标量多数仍然需要满足一些其他属性。但即使把上面的“梦想”转化为真实的代码,我也无法实现。
编辑:为了回应sds的回答,让我提出以下对原始问题的澄清:简而言之,似乎我需要依赖类型 Lisp,与仅仅参考参数类不同。事实上,Haskell具有参数类型,但没有(至少它没有以明显的方式内置)依赖类型。例如,在Haskell中缺少依赖类型是here。
1。任何人都可以帮我把我的梦想变成代码吗?
2。我听说过,由于Lisp宏,你不需要在Lisp中使用参数类。如果这是真的,有人可以解释你如何使用defmacro在Common Lisp中实现/假参数类吗?
答案 0 :(得分:3)
我怀疑你想要的东西是否有意义, 但作为宏(ab)使用的一个例子,你可以去:
(defmacro define-real-vector-space (type &optional name)
`(defclass ,(or name (intern (format nil "REAL-VECTOR-SPACE-~A" type))) ()
((V :reader underlying-set :initform ',type)
(vector-add :accessor add
:type (function ((x ,type) (y ,type)) => ,type))
(scalar-mult :accessor s-mult
:type (function ((x real) (v ,type) => ,type))))))
;; sample underlying set:
(deftype 3d () (array real (3)))
;; use it:
(macroexpand-1 '(define-real-vector-space 3d))
==>
(DEFCLASS REAL-VECTOR-SPACE-3D NIL
((V :READER UNDERLYING-SET :INITFORM '|3D|)
(VECTOR-ADD :ACCESSOR ADD :TYPE (FUNCTION ((X |3D|) (Y |3D|)) => |3D|))
(SCALAR-MULT :ACCESSOR S-MULT :TYPE #'((X REAL) (V |3D|) => |3D|))))
(define-real-vector-space 3d)
回应评论:
如果您想要一个单 real-vector-space
课程,那么基本上,
要求vector-add
和scalar-mult
位置具有哪种类型
取决于V
广告位的值
这意味着(setf (underlying-set rvs) some-new-type)
会
必须检查(add rvs)
和(s-mult rvs)
是否合适
some-new-type
的类型。
从本质上讲,这意味着要么是每个类型的对象
real-vector-space
是不可变的,所有插槽都被修改
同时。
前一种选择可以通过明智的使用来实现
MOP。
我不确定后者在Lisp中是否可行。
答案 1 :(得分:2)
您可以阅读来自FaréRideau的LIL: CLOS reaches higher-order, sheds identity and has a transformative experience中描述的 LIL , Lisp接口库。 github page有更多详细信息。
基本上,LIL尝试通过附加参数(接口,类似于类型类)来表达参数多态,由于动态范围,可以使其隐式。
另一方面,您想要表达的内容对OCaml模块来说非常容易,因此根据您的需求,您可以更好地遵循Rainer Joswig的建议(使用其他语言)。
module type VectorSpace =
functor (S : sig val dimension : int end)
(F : sig type scalar val zero : scalar end) ->
sig
type vector = F.scalar array
val add : vector -> vector -> vector
val mul : F.scalar -> vector -> vector
end
对于属性(在注释中请求),您可能需要使用更复杂的类型系统(Coq?)。 Common Lisp如何很好地抽象出事物的一个例子是Gábor Melis的MGL-CUBE。
答案 2 :(得分:0)
我们走了:我的问题的部分答案/解决方案(为什么部分?见下文)。非常感谢sds帮助我解决这个问题!
首先让我澄清一下。当我最初提出问题时,我使用的术语是参数类型'不精确地,只有一个模糊的定义作为一个类型依赖参数'心里。我基本上想要一些小工具,允许我编写以下伪代码(用伪语言):
class List<T> {
//implementation
};
l = new List<string>;
l.push("Hello World!");
理解上述伪代码非常简单(参见sds的回答)。然而,如果人们开始怀疑表达式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)))
基本上,我可以在这里复制粘贴sds的解决方案,但这有两个问题。首先,结果不是(无副作用)函数,例如形式
(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
将后者转换为前者(摘自书和元对象协议的艺术&#39;),宏defclassfamily
可以实现如下:< / p>
(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
,第一个是类型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这个古老而又可爱的混乱中似乎完全无法实现。