AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79277949
Accepted
myname
myname
Asked: 2024-12-13 18:23:18 +0800 CST2024-12-13 18:23:18 +0800 CST 2024-12-13 18:23:18 +0800 CST

我如何根据类定义我的类型,以便可以在 typecase 和相关表达式中使用该类型?

  • 772

考虑一下:

(defclass my-string ()
  ((data :initarg :data :type simple-string)
   (properties :initarg :properties :type interval-tree))
  (:documentation
   "Represents a string with properties."))

我想在 typecase 表达式中使用 my-string 作为类型:

(defun upcase (obj)
  (typecase obj
    (my-string (string-upcase (slot-value obj 'data)))
    (string (string-upcase obj))
    (integer (char-upcase (code-char obj)))
    (character (char-upcase obj))
    (otherwise (error "Wrong type argument ~S" obj))))

我以为类是类型,但显然不是,因为上面的代码是编译器错误。所以我声明了一个类型:

(deftype my-string (s) (typep s 'my-string))

看来我必须像这样使用它(否则我会收到编译错误,typedef 需要一个参数):

(defun upcase (obj)
  (typecase obj
    ((my-string obj) (string-upcase (slot-value obj 'data)))
    (string (string-upcase obj))
    (integer (char-upcase (code-char obj)))
    (character (char-upcase obj))
    (otherwise (error "Wrong type argument ~S" obj))))

但是 SBCL 删除了我的代码,因为无法访问!:-)

我该怎么做?如何正确声明类的类型,以便可以在类型声明可用的表达式中使用它?

我知道我可以使用结构而不是类,在这种情况下,编译器会为该类型生成代码,并且初始类型“正常工作”:

(defstruct my-string
  data properties)

宏扩展和查看生成的代码并没有真正让我更加明白。我看到的是:

(SB-C:XDEFUN MY-STRING-P
     :PREDICATE
     NIL
     (SB-KERNEL::OBJECT)
   (TYPEP SB-KERNEL::OBJECT 'MY-STRING))

它确实做了我所做的事,但我认为该函数不参与其中,还是我错了?要么他们以某种方式在内部将结构与类型关联起来,要么我错过了生成代码的一些相关部分?我对这些都不是很熟悉,所以也很有可能。

一个相关的问题:在 CommonLisp 中,对扩展内置类型(如字符串)的自定义类进行建模的正确方法是什么?像在 my-string 中所做的那样,进行组合是一种更好的方法吗?还是从某个内置的 CL 字符串类继承更好?基于这个 SX 问题和答案,在我看来,继承并不是对类型进行建模的最佳方式,这可能是更好的,因为我们不喜欢 Java 或旧 C++ 中的分类法。

最后,在这种情况下,我甚至不需要 typecase,如果我将 upcase 变成一个专门针对这些类型的通用方法,整个问题就消失了,我完全知道这一点。但是,我想更多地了解类型和类,以及如何在 CL 中对类似上述内容进行建模。

抱歉,这篇文章有点长,而且一个问题太多了,我才刚刚开始学习。感觉我遇到的每一个问题和寻找答案的尝试都会引出另外 10 个问题 :)。

common-lisp
  • 4 4 个回答
  • 59 Views

4 个回答

  • Voted
  1. Best Answer
    coredump
    2024-12-13T22:49:13+08:002024-12-13T22:49:13+08:00

    您的使用deftype方式完全错误,因此,尽管有更好的答案,但我还是想关注错误的部分。

    当您编写 时(deftype u (s) (typep s 'u)),第一次您不会收到任何警告,但之后如果您再次尝试(使用 SBCL),则会在编译期间收到警告,因为u中的(typep s 'u)参数数量不符合 的定义(deftype u (s) ...)。一旦我们理解了 的含义,就可以解释这一点deftype。

    首先,为了使(typep ...)调用有意义,您需要编写类似这样的内容,(typep v '(u s))其中v是一个值,并且(u s)是一个类型,由参数 参数化s。例如,(integer -2 -2)是这样一种类型,它是 的子集,integer具有包含边界:

    (loop for i from -5 to 5 collect (list i (typep i '(integer -2 2))))
    => ((-5 NIL) (-4 NIL) (-3 NIL)
        (-2 T) (-1 T) (0 T) (1 T) (2 T) 
        (3 NIL) (4 NIL) (5 NIL))
    

    带有 的值T是成功时的值typep。这是因为您定义的函数deftype必须返回类型表达式,通常您会发现类似以下内容:

    (deftype octet () `(unsigned-byte 8))
    

    或者

    (deftype square-matrix (size type)
      `(array ,type (,size ,size)))
    

    通过上述定义,您可以检查数组是否实际上是整数的方阵:

    (typep #2A((1 1) (2 2)) '(square-matrix 2 integer))
    

    产生类型说明符square-matrix的函数也是如此,如4.2.3 类型说明符中所定义。

    您的my-string函数返回一个广义布尔值(的结果typep),而您想要给出的参数是一个值(类型为),my-string而s在deftype参数中应该用来表达类型参数,因为您可能有不同的变化my-string(例如,您的字符串的明确大小参数)。

    我希望这能使事情稍微澄清一些。


    此外,类是一种类型,因此可以这样:

    (defclass foo () ())
    
    (defun bar (v)
      (typecase v
        (string "a string")
        (foo "a foo")))
    
    • 3
  2. Rainer Joswig
    2024-12-15T17:32:52+08:002024-12-15T17:32:52+08:00

    任何 CLOS 类都有相应的类型。

    CL-USER 3 > (defclass foo () (a b c))
    #<STANDARD-CLASS FOO 801000157B>
    
    CL-USER 4 > (type-of *)
    STANDARD-CLASS
    
    CL-USER 5 > (make-instance 'foo)
    #<FOO 801000568B>
    
    CL-USER 6 > (type-of *)
    FOO
    
    CL-USER 7 > (typep (make-instance 'foo) 'foo)
    T
    

    该类的类对象FOO具有类型STANDARD-CLASS。

    该类的实例FOO属于类型FOO。

    因此TYPECASE适用于 CLOS 类的类型名称:

    CL-USER 8 > (dolist (object (list 1 "2" (make-instance 'foo)))
                  (typecase object
                    (number (format t "~% ~s is a number" object)) 
                    (string (format t "~% ~s is a string" object)) 
                    (foo    (format t "~% ~s is a CLOS object of class ~a"
                                    object (class-of object)))))
    
     1 is a number
     "2" is a string
     #<FOO 8010000E2B> is a CLOS object of class #<STANDARD-CLASS FOO 81400243A3>
    
    • 2
  3. Gwang-Jin Kim
    2024-12-13T20:02:57+08:002024-12-13T20:02:57+08:00

    您最好使用 Common Lisp CLOS 的多重分派功能来实现类行为(在这种情况下,只需要单次分派) - 通过使用通用函数宏defgeneric和defmethod。

    无论如何,使用调度的好处在于您可以更加灵活,并且类选择以后可以扩展(维护起来更好)。不仅如此 - 如果您导入此代码(例如作为包) - 使用此代码的代码(嵌入在包中的此代码) - 仍然可以扩展调度(为调度添加新案例/新类),这是编程中非常强大的功能。

    并且调度能力优于typecase。typecase当然速度更快。

    (defclass my-string ()
      ((data :initarg :data :type simple-string)
       (properties :initarg :properties :type interval-tree))
      (:documentation
       "Represents a string with properties."))
    

    首先使用以下方法编写一个通用函数defgeneric:

    (defgeneric upcase (obj)
      (:documentation "A generic function to upcase different types of objects."))
    

    defmethod然后,使用每个 auf 子句作为单独的方法函数进行编写:

    ;; the general form is:
    ;; (defmethod upcase ((obj the-type-or-class-of-obj))
    ;;    ... specific code handling `obj` ...)
    
    
    (defmethod upcase ((obj my-string))
      (string-upcase (slot-value obj 'data)))
    
    (defmethod upcase ((obj string))
      (string-upcase obj))
    
    (defmethod upcase ((obj integer))
      (char-upcase (code-char obj)))
    
    (defmethod upcase ((obj character))
      (char-upcase obj))
    
    (defmethod upcase ((obj t))
      (error "Wrong type argument ~S" obj))
    

    通过以下方式尝试代码:

    (let ((custom-string (make-instance 'my-string :data "hello" :properties nil)))
      (format t "Upcased my-string: ~A~%" (upcase custom-string)))
    
    (format t "Upcased plain string: ~A~%" (upcase "world"))
    
    (format t "Upcased integer: ~A~%" (upcase 97))  ; ASCII code for 'a'
    
    (format t "Upcased character: ~A~%" (upcase #\a))
    
    (handler-case
        (upcase '(1 2 3))
      (error (e) (format t "Error: ~A~%" e)))
    
    ;; The output should be:
    Upcased my-string: HELLO ; NIL
    Upcased plain string: WORLD ; NIL
    Upcased integer: A ; NIL
    Upcased character: A ; NIL
    Error: Wrong type argument (1 2 3) ; NIL
    
    • 1
  4. Gwang-Jin Kim
    2024-12-14T16:06:21+08:002024-12-14T16:06:21+08:00

    @coredump 回答正确。

    在某些情况下,通过typecase谓词进行调度可能更有意义。

    你可以编写一个宏:

    (defmacro predcase (obj &rest clauses)
      "A version of TYPECASE that uses predicates."
      `(cond
         ,@(mapcar (lambda (clause)
                     (let ((predicate (car clause))
                           (body (cdr clause)))
                       `((,predicate ,obj) ,@body)))
                   clauses)))
    

    然后像这样使用它:

    (defun upcase (obj)
      (predcase obj
        (my-string-p
         (string-upcase (slot-value obj 'data)))
        (stringp
         (string-upcase obj))
        (integerp
         (char-upcase (code-char obj)))
        (characterp
         (char-upcase obj))
        (t
         (error "Wrong type argument ~S" obj))))
    

    但我知道这实际上不是问题所在。

    • 1

相关问题

  • 使用 Quicklisp 和 Portacle 识别本地项目/定义系统

  • 无法使 cl-ppcre 在多行模式下工作

  • 使用“fboundp”进行“cond”测试时出现警告,为什么?

  • 当结构相互指向时无限打印

  • 为什么使用“let”时“setf”不起作用?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行?

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    何时应使用 std::inplace_vector 而不是 std::vector?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Martin Hope
    Aleksandr Dubinsky 为什么 InetAddress 上的 switch 模式匹配会失败,并出现“未涵盖所有可能的输入值”? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge 为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini 具有指定基础类型但没有枚举器的“枚举类”的用途是什么? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer 何时应使用 std::inplace_vector 而不是 std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB 为什么 GCC 生成有条件执行 SIMD 实现的代码? 2024-02-17 06:17:14 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve