如何解决如何检查 Common Lisp 中的两种类型是否相同? 简单的演员表可扩展的演员另见
我正在学习 Common Lisp,现在正在尝试复制 C++ (boost) lexical_cast<bool>(string)
。但是在 CL 中找不到比较类型的方法。
C++ 版本看起来像这样
template <typename ret_type>
ret_type lexical_cast(const std::string& val)
{
if constexpr (std::is_same_v<ret_type,bool>)
return val == "true";
// Otherwise don't care
}
到目前为止,我想出了以下使用 equal
比较类型的 Lisp 代码。我认为这会起作用,因为 Lisp 对数据和代码漠不关心。
(defun lexical_cast(ret_type val)
(if (equal ret_type #'boolean) (equalp val "true"))
; Otherwise don't case
)
但 SBCL 产生错误:
; in: DEFUN LEXICAL_CAST
; #'BOOLEAN
;
; caught WARNING:
; The function BOOLEAN is undefined,and its name is reserved by ANSI CL so that
; even if it were defined later,the code doing so would not be portable.
;
; compilation unit finished
; Undefined function:
; BOOLEAN
; caught 1 WARNING condition
nor typep
也有帮助,因为它将对象的类型与另一种类型进行比较。不直接比较。我搜索了 Lisp 教程和手册,但没有成功。我该怎么做?
解决方法
Common Lisp 定义了 SUBTYPEP
来比较类型。使用它,您可以定义一个类型相等比较函数:
(defun type-equal (t1 t2)
(and (subtypep t1 t2)
(subtypep t2 t1)))
例如,对 boolean
类型进行编码有不同的方式,但它们覆盖相同的一组值,因此它们是相等的:
(type-equal '(member t nil) '(or (eql t) (eql nil)))
=> T,T
还有一个 COERCE
函数,它允许以有限的方式将值转换为另一种类型。然而,这是不可扩展的。
简单的演员表
让我们首先为不知道如何将值转换为目标类型的情况定义一个新条件。为简单起见,此错误没有关联数据:
(define-condition cast-error (error) ())
那么,对 lexical-cast
的严格但简单的定义可以是:
(defun lexical-cast (return-type value)
(coerce (cond
((type-equal return-type 'boolean)
(typecase value
(string (string= value "true"))
(real (/= value 0))
(t (error 'cast-error))))
((subtypep return-type 'real)
(typecase value
(string (read-from-string value))
(boolean (if value 1 0))
(t (error 'cast-error))))
(t
(error 'cast-error)))
return-type))
我将 cond
形式包装在 coerce
形式中,以检查内部结果是否具有适当的返回类型。这有助于检测实施错误。
内部表单对返回类型 (cond
) 和值的类型 (typecase
s) 执行多次分派。例如,您可以从字符串转换为布尔值:
(lexical-cast '(member t nil) "true")
T
假设我们遵循 0 为假的 C 约定,这里是一个数字的转换(注意布尔类型的表达方式不同):
(lexical-cast '(or (eql t) (eql nil)) 0)
NIL
还要注意,Common Lisp 允许您定义范围。这里我们有一个错误,因为从字符串中读取给出的值超出了例外范围(注意:从字符串中读取对于某些输入可能不安全,但这超出了此处的范围)。
(lexical-cast '(integer 0 10) "50")
;; error from COERCE
(lexical-cast '(integer 0 10) "5")
5
这是另一个示例,从布尔值转换为实数的子类型:
(lexical-cast 'bit t)
1
可扩展的演员
为了拥有通用的可扩展词法转换,例如 PPRINT-DISPATCH
,您需要维护不同类型的转换函数表,同时确保始终选择与返回类型匹配的最接近的类型。>
一种非常简单的方法是为某些专用返回类型定义单独的通用转换函数:
;; can be extended for other classes of values
(defgeneric cast-to-boolean (value)
(:method ((v symbol)) v)
(:method ((v integer)) (eql v 1))
(:method ((v string)) (string= v "true")))
然后,您维护从类型到转换函数的映射:
;; usually this is hidden behind user-friendly macros
(defvar *type-cast* nil)
;; this could be written instead (set-cast 'boolean #'cast-to-boolean)
;; for example,but here we just set the list directly.
(setf *type-cast*
(acons 'boolean #'cast-to-boolean *type-cast*))
然后,您有一个单一的强制转换函数,它查找与返回类型相等的第一个类型,并调用关联的函数:
(defun gen-type-cast (return-type value)
(let ((f (assoc return-type *type-cast* :test #'type-equal)))
(if f (funcall (cdr f) value) (error 'cast-error))))
例如:
(gen-type-cast '(member t nil) "true")
T
另见
lisp-types
系统有助于分析和简化类型表达式。
我的错。我在发布问题后立即发现了问题。 # 不应该在那里
(defun lexical_cast(ret_type val)
(if (equal ret_type 'boolean) (equalp val "true"))
; Otherwise don't case
)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。