Search code examples
typescommon-lispslotsclos

Comprehensive guide on common lisp types


Maybe this question is too general, nevertheless i'll try: Is there any comprehensive guide on types in common lisp?

I'm kind of confused about this subject:

Why are non-primitive types declared in make-array's :element-type are promoted to t? Is there any possibility for compile-time or runtime checks of the real declared type?

Why are CLOS slot defined types don't work as constraints, allowing to put value of any type into the slot? Again, what about the checks?

The same for the functions' types declarations with declare.. Are they just the optimization hints to the compiler?

Also, can I use custom type specifiers, including satisfies in forementioned places for some robust checks, or they could only be used for explicit checks with typep e.t.c?

As you can see, i've got some mess in my head, so would really appreciate any neat guide (or a set of guides).

I'm on SBCL, but would also be glad to know about differences between implementations.


Solution

  • You need to tell the compiler to optimize for safety if you want it to actually enforce the types:

    CL-USER> (declaim (optimize (safety 3)))
    NIL
    CL-USER> (defclass foobar () ())
    #<STANDARD-CLASS COMMON-LISP-USER::FOOBAR>
    CL-USER> (defun foo (a)
               (make-array 1 :element-type 'foobar
                             :initial-contents (list a)))
    FOO
    CL-USER> (foo (make-instance 'foobar))
    #(#<FOOBAR {1005696CE3}>)
    CL-USER> (foo 12)
    ;=> ERROR
    CL-USER> (declaim (ftype (function (integer integer) integer) quux))
    (QUUX)
    CL-USER> (defun quux (a b)
               (+ a b))
    QUUX
    CL-USER> (quux 12 12)
    24 (5 bits, #x18, #o30, #b11000)
    CL-USER> (quux 12 "asd")
    ;=> ERROR
    

    Checking the types at run-time adds some overhead (especially if it happens in a loop), and may be done multiple times for a single value, so it's not done by default.

    (declaim (optimize (safety 3)))
    
    (defun some-predicate-p (a)
      (format t "~&Checking type...")
      (integerp a))
    
    (deftype foo () `(satisfies some-predicate-p))
    
    (defclass bar ()
      ((foo :type foo :initarg :foo)))
    
    (declaim (ftype (function (foo) list) qwerty))
    (defun qwerty (foo)
      (loop repeat 10 collecting (make-instance 'bar :foo foo)))
    
    (qwerty 12)
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ; Checking type...
    ;=> (#<BAR {1003BCA213}> #<BAR {1003BCA263}> #<BAR {1003BCA2B3}>
    ;    #<BAR {1003BCA303}> #<BAR {1003BCA353}> #<BAR {1003BCA3A3}>
    ;    #<BAR {1003BCA3F3}> #<BAR {1003BCA443}> #<BAR {1003BCA493}>
    ;    #<BAR {1003BCA4E3}>)
    

    If you want a function to always check the type of a place, regardless of the optimization settings, you should use CHECK-TYPE manually.