682 lines
26 KiB
HTML
682 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta name="generator" content=
|
||
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
||
<title>Type System</title>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="A collection of examples of using Common Lisp">
|
||
<meta name="viewport" content=
|
||
"width=device-width, initial-scale=1">
|
||
<link rel="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<link rel="stylesheet" href=
|
||
"assets/style.css">
|
||
<script type="text/javascript" src=
|
||
"assets/highlight-lisp.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery-3.2.1.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery.toc/jquery.toc.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/toggle-toc.js">
|
||
</script>
|
||
|
||
<link rel="stylesheet" href=
|
||
"assets/github.css">
|
||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||
</head>
|
||
<body>
|
||
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – Type System</h1>
|
||
<div id="logo-container">
|
||
<a href="index.html">
|
||
<img id="logo" src="assets/cl-logo-blue.png"/>
|
||
</a>
|
||
|
||
<div id="searchform-container">
|
||
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
||
<input id="searchField" type="text" value="" placeholder="Search...">
|
||
</form>
|
||
</div>
|
||
|
||
<div id="toc-container" class="toc-close">
|
||
<div id="toc-title">Table of Contents</div>
|
||
<ul id="toc" class="list-unstyled"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content-container">
|
||
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – Type System</h1>
|
||
|
||
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
||
<!-- <p class="announce"> -->
|
||
<!-- 📢 🤶 ⭐ -->
|
||
<!-- <a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a> -->
|
||
<!-- <strong> -->
|
||
<!-- Recently added: 18 videos on MACROS. -->
|
||
<!-- </strong> -->
|
||
<!-- <a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>. -->
|
||
<!-- </p> -->
|
||
|
||
<p class="announce">
|
||
📢 New videos: <a href="https://www.youtube.com/watch?v=h_noB1sI_e8">web dev demo part 1</a>, <a href="https://www.youtube.com/watch?v=xnwc7irnc8k">dynamic page with HTMX</a>, <a href="https://www.youtube.com/watch?v=Zpn86AQRVN8">Weblocks demo</a>
|
||
</p>
|
||
|
||
<p class="announce-neutral">
|
||
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
||
</p>
|
||
|
||
|
||
<div id="content"
|
||
<p>Common Lisp has a complete and flexible type system and corresponding
|
||
tools to inspect, check and manipulate types. It allows creating
|
||
custom types, adding type declarations to variables and functions and
|
||
thus to get compile-time warnings and errors.</p>
|
||
|
||
<h2 id="values-have-types-not-variables">Values Have Types, Not Variables</h2>
|
||
|
||
<p>Being different from some languages such as C/C++, variables in Lisp are just
|
||
<em>placeholders</em> for objects<sup id="fnref:1" role="doc-noteref"><a href="type.html#fn:1" class="footnote" rel="footnote">1</a></sup>. When you <a href="http://www.lispworks.com/documentation/lw50/CLHS/Body/m_setf_.htm"><code>setf</code></a> a variable, an object
|
||
is “placed” in it. You can place another value to the same variable later, as
|
||
you wish.</p>
|
||
|
||
<p>This implies a fact that in Common Lisp <strong>objects have types</strong>, while
|
||
variables do not. This might be surprising at first if you come from a C/C++
|
||
background.</p>
|
||
|
||
<p>For example:</p>
|
||
|
||
<pre><code class="language-lisp">(defvar *var* 1234)
|
||
*VAR*
|
||
|
||
(type-of *var*)
|
||
(INTEGER 0 4611686018427387903)
|
||
</code></pre>
|
||
|
||
<p>The function <a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_tp_of.htm"><code>type-of</code></a> returns the type of the given object. The
|
||
returned result is a <a href="http://www.lispworks.com/documentation/lw51/CLHS/Body/04_bc.htm">type-specifier</a>. In this case the first
|
||
element is the type and the remaining part is extra information (lower and
|
||
upper bound) of that type. You can safely ignore it for now. Also remember
|
||
that integers in Lisp have no limit!</p>
|
||
|
||
<p>Now let’s try to <a href="http://www.lispworks.com/documentation/lw50/CLHS/Body/m_setf_.htm"><code>setf</code></a> the variable:</p>
|
||
|
||
<pre><code class="language-lisp">* (setf *var* "hello")
|
||
"hello"
|
||
|
||
* (type-of *var*)
|
||
(SIMPLE-ARRAY CHARACTER (5))
|
||
</code></pre>
|
||
|
||
<p>You see, <code>type-of</code> returns a different result: <a href="http://www.lispworks.com/documentation/lw70/CLHS/Body/t_smp_ar.htm"><code>simple-array</code></a>
|
||
of length 5 with contents of type <a href="http://www.lispworks.com/documentation/lcl50/ics/ics-14.html"><code>character</code></a>. This is because
|
||
<code>*var*</code> is evaluated to string <code>"hello"</code> and the function <code>type-of</code> actually
|
||
returns the type of object <code>"hello"</code> instead of variable <code>*var*</code>.</p>
|
||
|
||
<h2 id="type-hierarchy">Type Hierarchy</h2>
|
||
|
||
<p>The inheritance relationship of Lisp types consists a type graph and the root
|
||
of all types is <code>T</code>. For example:</p>
|
||
|
||
<pre><code class="language-lisp">* (describe 'integer)
|
||
COMMON-LISP:INTEGER
|
||
[symbol]
|
||
|
||
INTEGER names the built-in-class #<BUILT-IN-CLASS COMMON-LISP:INTEGER>:
|
||
Class precedence-list: INTEGER, RATIONAL, REAL, NUMBER, T
|
||
Direct superclasses: RATIONAL
|
||
Direct subclasses: FIXNUM, BIGNUM
|
||
No direct slots.
|
||
|
||
INTEGER names a primitive type-specifier:
|
||
Lambda-list: (&OPTIONAL (SB-KERNEL::LOW '*) (SB-KERNEL::HIGH '*))
|
||
</code></pre>
|
||
|
||
<p>The function <a href="http://www.lispworks.com/documentation/lw51/CLHS/Body/f_descri.htm"><code>describe</code></a> shows that the symbol <a href="http://www.lispworks.com/documentation/lw71/CLHS/Body/t_intege.htm"><code>integer</code></a>
|
||
is a primitive type-specifier that has optional information lower bound and
|
||
upper bound. Meanwhile, it is a built-in class. But why?</p>
|
||
|
||
<p>Most common Lisp types are implemented as CLOS classes. Some types are simply
|
||
“wrappers” of other types. Each CLOS class maps to a corresponding type. In
|
||
Lisp types are referred to indirectly by the use of <a href="http://www.lispworks.com/documentation/lw51/CLHS/Body/04_bc.htm"><code>type
|
||
specifiers</code></a>.</p>
|
||
|
||
<p>There are some differences between the function <a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_tp_of.htm"><code>type-of</code></a> and
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_clas_1.htm"><code>class-of</code></a>. The function <code>type-of</code> returns the type of a given
|
||
object in type specifier format while <code>class-of</code> returns the implementation
|
||
details.</p>
|
||
|
||
<pre><code class="language-lisp">* (type-of 1234)
|
||
(INTEGER 0 4611686018427387903)
|
||
|
||
* (class-of 1234)
|
||
#<BUILT-IN-CLASS COMMON-LISP:FIXNUM>
|
||
</code></pre>
|
||
|
||
<h2 id="checking-types">Checking Types</h2>
|
||
|
||
<p>The function <a href="http://www.lispworks.com/documentation/lw51/CLHS/Body/f_typep.htm"><code>typep</code></a> can be used to check if the first argument is of
|
||
the given type specified by the second argument.</p>
|
||
|
||
<pre><code class="language-lisp">* (typep 1234 'integer)
|
||
T
|
||
</code></pre>
|
||
|
||
<p>The function <a href="http://www.lispworks.com/documentation/lw71/CLHS/Body/f_subtpp.htm"><code>subtypep</code></a> can be used to inspect if a type inherits
|
||
from the another one. It returns 2 values:</p>
|
||
|
||
<ul>
|
||
<li><code>T, T</code> means first argument is sub-type of the second one.</li>
|
||
<li><code>NIL, T</code> means first argument is <em>not</em> sub-type of the second one.</li>
|
||
<li><code>NIL, NIL</code> means “not determined”.</li>
|
||
</ul>
|
||
|
||
<p>For example:</p>
|
||
|
||
<pre><code class="language-lisp">* (subtypep 'integer 'number)
|
||
T
|
||
T
|
||
|
||
* (subtypep 'string 'number)
|
||
NIL
|
||
T
|
||
</code></pre>
|
||
|
||
<p>Sometimes you may want to perform different actions according to the type of
|
||
an argument. The macro <a href="http://www.lispworks.com/documentation/lw60/CLHS/Body/m_tpcase.htm"><code>typecase</code></a> is your friend:</p>
|
||
|
||
<pre><code class="language-lisp">* (defun plus1 (arg)
|
||
(typecase arg
|
||
(integer (+ arg 1))
|
||
(string (concatenate 'string arg "1"))
|
||
(t 'error)))
|
||
PLUS1
|
||
|
||
* (plus1 100)
|
||
101 (7 bits, #x65, #o145, #b1100101)
|
||
|
||
* (plus1 "hello")
|
||
"hello1"
|
||
|
||
* (plus1 'hello)
|
||
ERROR
|
||
</code></pre>
|
||
|
||
<h2 id="type-specifier">Type Specifier</h2>
|
||
|
||
<p>A type specifier is a form specifying a type. As mentioned above, returning
|
||
value of the function <code>type-of</code> and the second argument of <code>typep</code> are both
|
||
type specifiers.</p>
|
||
|
||
<p>As shown above, <code>(type-of 1234)</code> returns <code>(INTEGER 0
|
||
4611686018427387903)</code>. This kind of type specifiers are called compound type
|
||
specifier. It is a list whose head is a symbol indicating the type. The rest
|
||
part of it is complementary information.</p>
|
||
|
||
<pre><code class="language-lisp">* (typep '#(1 2 3) '(vector number 3))
|
||
T
|
||
</code></pre>
|
||
|
||
<p>Here the complementary information of the type <code>vector</code> is its elements type
|
||
and size respectively.</p>
|
||
|
||
<p>The rest part of a compound type specifier can be a <code>*</code>, which means
|
||
“anything”. For example, the type specifier <code>(vector number *)</code> denotes a
|
||
vector consisting of any number of numbers.</p>
|
||
|
||
<pre><code class="language-lisp">* (typep '#(1 2 3) '(vector number *))
|
||
T
|
||
</code></pre>
|
||
|
||
<p>The trailing parts can be omitted, the omitted elements are treated as
|
||
<code>*</code>s:</p>
|
||
|
||
<pre><code class="language-lisp">* (typep '#(1 2 3) '(vector number))
|
||
T
|
||
|
||
* (typep '#(1 2 3) '(vector))
|
||
T
|
||
</code></pre>
|
||
|
||
<p>As you may have guessed, the type specifier above can be shortened as
|
||
following:</p>
|
||
|
||
<pre><code class="language-lisp">* (typep '#(1 2 3) 'vector)
|
||
T
|
||
</code></pre>
|
||
|
||
<p>You may refer to the <a href="http://www.lispworks.com/documentation/lw51/CLHS/Body/04_bc.htm">CLHS page</a> for more information.</p>
|
||
|
||
<h2 id="defining-new-types">Defining New Types</h2>
|
||
|
||
<p>You can use the macro <a href="http://www.lispworks.com/documentation/lw51/CLHS/Body/m_deftp.htm"><code>deftype</code></a> to define a new type-specifier.</p>
|
||
|
||
<p>Its argument list can be understood as a direct mapping to elements of rest
|
||
part of a compound type specifier. They are defined as optional to allow
|
||
symbol type specifier.</p>
|
||
|
||
<p>Its body should be a macro checking whether given argument is of this type
|
||
(see <a href="http://www.lispworks.com/documentation/lw70/CLHS/Body/m_defmac.htm"><code>defmacro</code></a>).</p>
|
||
|
||
<p>We can use <code>member</code> to define enum types, for example:</p>
|
||
|
||
<pre><code class="language-lisp">(deftype fruit () '(member :apple :orange :pear))
|
||
</code></pre>
|
||
|
||
<p>Now let us define a new data type. The data type should be a array with at
|
||
most 10 elements. Also each element should be a number smaller than 10. See
|
||
following code for an example:</p>
|
||
|
||
<pre><code class="language-lisp">* (defun small-number-array-p (thing)
|
||
(and (arrayp thing)
|
||
(<= (length thing) 10)
|
||
(every #'numberp thing)
|
||
(every (lambda (x) (< x 10)) thing)))
|
||
|
||
* (deftype small-number-array (&optional type)
|
||
`(and (array ,type 1)
|
||
(satisfies small-number-array-p)))
|
||
|
||
* (typep '#(1 2 3 4) '(small-number-array number))
|
||
T
|
||
|
||
* (typep '#(1 2 3 4) 'small-number-array)
|
||
T
|
||
|
||
* (typep '#(1 2 3 4 100) 'small-number-array)
|
||
NIL
|
||
|
||
* (small-number-array-p '#(1 2 3 4 5 6 7 8 9 0 1))
|
||
NIL
|
||
</code></pre>
|
||
|
||
<h2 id="run-time-type-checking">Run-time type Checking</h2>
|
||
|
||
<p>Common Lisp supports run-time type checking via the macro
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_check_.htm#check-type"><code>check-type</code></a>. It accepts a <a href="http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_p.htm#place"><code>place</code></a> and a type specifier
|
||
as arguments and signals an <a href="http://www.lispworks.com/documentation/HyperSpec/Body/e_tp_err.htm#type-error"><code>type-error</code></a> if the contents of
|
||
place are not of the given type.</p>
|
||
|
||
<pre><code class="language-lisp">* (defun plus1 (arg)
|
||
(check-type arg number)
|
||
(1+ arg))
|
||
PLUS1
|
||
|
||
* (plus1 1)
|
||
2 (2 bits, #x2, #o2, #b10)
|
||
|
||
* (plus1 "hello")
|
||
; Debugger entered on #<SIMPLE-TYPE-ERROR expected-type: NUMBER datum: "Hello">
|
||
|
||
The value of ARG is "Hello", which is not of type NUMBER.
|
||
[Condition of type SIMPLE-TYPE-ERROR]
|
||
...
|
||
</code></pre>
|
||
|
||
<h2 id="compile-time-type-checking">Compile-time type checking</h2>
|
||
|
||
<p>You may provide type information for variables, function arguments
|
||
etc via <a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_procla.htm"><code>proclaim</code></a>, <a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_declai.htm"><code>declaim</code></a> (at the toplevel) and <a href="http://www.lispworks.com/documentation/HyperSpec/Body/s_declar.htm"><code>declare</code></a> (inside functions and macros).</p>
|
||
|
||
<p>However, similar to the <code>:type</code> slot
|
||
introduced in <a href="clos.html">CLOS section</a>, the effects of type declarations are
|
||
undefined in Lisp standard and are implementation specific. So there is no
|
||
guarantee that the Lisp compiler will perform compile-time type checking.</p>
|
||
|
||
<p>However, it is possible, and SBCL is an implementation that does
|
||
thorough type checking.</p>
|
||
|
||
<p>Let’s recall first that Lisp already warns about simple type
|
||
warnings. The following function wrongly wants to concatenate a string
|
||
and a number. When we compile it, we get a type warning.</p>
|
||
|
||
<pre><code class="language-lisp">(defconstant +foo+ 3)
|
||
(defun bar ()
|
||
(concatenate 'string "+" +foo+))
|
||
; caught WARNING:
|
||
; Constant 3 conflicts with its asserted type SEQUENCE.
|
||
; See also:
|
||
; The SBCL Manual, Node "Handling of Types"
|
||
</code></pre>
|
||
|
||
<p>The example is simple, but it already shows a capacity some other
|
||
languages don’t have, and it is actually useful during development ;)
|
||
Now, we’ll do better.</p>
|
||
|
||
<h3 id="declaring-the-type-of-variables">Declaring the type of variables</h3>
|
||
|
||
<p>Use the macro <a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_declai.htm"><code>declaim</code></a> with a <code>type</code> declaration identifier (other identifiers are “ftype, inline, notinline, optimize…).</p>
|
||
|
||
<p>Let’s declare that our global variable <code>*name*</code> is a string. You can
|
||
type the following in any order in the REPL:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (type (string) *name*))
|
||
(defparameter *name* "book")
|
||
</code></pre>
|
||
|
||
<p>Now if we try to set it with a bad type, we get a <code>simple-type-error</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(setf *name* :me)
|
||
Value of :ME in (THE STRING :ME) is :ME, not a STRING.
|
||
[Condition of type SIMPLE-TYPE-ERROR]
|
||
</code></pre>
|
||
|
||
<p>We can do the same with our custom types. Let’s quickly declare the type <code>list-of-strings</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(defun list-of-strings-p (list)
|
||
"Return t if LIST is non nil and contains only strings."
|
||
(and (consp list)
|
||
(every #'stringp list)))
|
||
|
||
(deftype list-of-strings ()
|
||
`(satisfies list-of-strings-p))
|
||
</code></pre>
|
||
|
||
<p>Now let’s declare that our <code>*all-names*</code> variables is a list of strings:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (type (list-of-strings) *all-names*))
|
||
;; and with a wrong value:
|
||
(defparameter *all-names* "")
|
||
;; we get an error, still at compile-time:
|
||
Cannot set SYMBOL-VALUE of *ALL-NAMES* to "", not of type
|
||
(SATISFIES LIST-OF-STRINGS-P).
|
||
[Condition of type SIMPLE-TYPE-ERROR]
|
||
</code></pre>
|
||
|
||
<h3 id="composing-types">Composing types</h3>
|
||
|
||
<p>We can compose types. Following the previous example:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (type (or null list-of-strings) *all-names*))
|
||
</code></pre>
|
||
|
||
<h3 id="declaring-the-input-and-output-types-of-functions">Declaring the input and output types of functions</h3>
|
||
|
||
<p>We use again the <code>declaim</code> macro, with <code>ftype (function …)</code> instead of just <code>type</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (ftype (function (fixnum) fixnum) add))
|
||
;; ^^input ^^output [optional]
|
||
(defun add (n)
|
||
(+ n 1))
|
||
</code></pre>
|
||
|
||
<p>With this we get nice type warnings at compile time.</p>
|
||
|
||
<p>If we change the function to erroneously return a string instead of a
|
||
fixnum, we get a warning:</p>
|
||
|
||
<pre><code class="language-lisp">(defun add (n)
|
||
(format nil "~a" (+ n 1)))
|
||
; caught WARNING:
|
||
; Derived type of ((GET-OUTPUT-STREAM-STRING STREAM)) is
|
||
; (VALUES SIMPLE-STRING &OPTIONAL),
|
||
; conflicting with the declared function return type
|
||
; (VALUES FIXNUM &REST T).
|
||
</code></pre>
|
||
|
||
<p>If we use <code>add</code> inside another function, to a place that expects a
|
||
string, we get a warning:</p>
|
||
|
||
<pre><code class="language-lisp">(defun bad-concat (n)
|
||
(concatenate 'string (add n)))
|
||
; caught WARNING:
|
||
; Derived type of (ADD N) is
|
||
; (VALUES FIXNUM &REST T),
|
||
; conflicting with its asserted type
|
||
; SEQUENCE.
|
||
</code></pre>
|
||
|
||
<p>If we use <code>add</code> inside another function, and that function declares
|
||
its argument types which appear to be incompatible with those of
|
||
<code>add</code>, we get a warning:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (ftype (function (string)) bad-arg))
|
||
(defun bad-arg (n)
|
||
(add n))
|
||
; caught WARNING:
|
||
; Derived type of N is
|
||
; (VALUES STRING &OPTIONAL),
|
||
; conflicting with its asserted type
|
||
; FIXNUM.
|
||
</code></pre>
|
||
|
||
<p>This all happens indeed <em>at compile time</em>, either in the REPL,
|
||
either with a simple <code>C-c C-c</code> in Slime, or when we <code>load</code> a file.</p>
|
||
|
||
<h3 id="declaring-key-parameters">Declaring &key parameters</h3>
|
||
|
||
<p>Use <code>&key (:argument type)</code>.</p>
|
||
|
||
<p>For example:</p>
|
||
|
||
<pre><code>(declaim (ftype (function (string &key (:n integer))) foo))
|
||
(defun foo (bar &key n) …)
|
||
</code></pre>
|
||
|
||
<h3 id="declaring-rest-parameters">Declaring &rest parameters</h3>
|
||
|
||
<p>This is less evident, you might need a well-placed <code>declare</code>.</p>
|
||
|
||
<p>In the following, we declare a fruit type and we write a function that
|
||
uses a single fruit argument, so compiling <code>placing-order</code> gives us a
|
||
type warning as expected:</p>
|
||
|
||
<pre><code class="language-lisp">(deftype fruit () '(member :apple :orange :pear))
|
||
|
||
(declaim (ftype (function (fruit)) one-order))
|
||
(defun one-order (fruit)
|
||
(format t "Ordering ~S~%" fruit))
|
||
|
||
(defun placing-order ()
|
||
(one-order :bacon))
|
||
</code></pre>
|
||
|
||
<p>But in this version, we use <code>&rest</code> parameters, and we don’t have a type warning anymore:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (ftype (function (&rest fruit)) place-order))
|
||
(defun place-order (&rest selections)
|
||
(dolist (s selections)
|
||
(format t "Ordering ~S~%" s)))
|
||
|
||
(defun placing-orders ()
|
||
(place-order :orange :apple :bacon)) ;; => no type warning
|
||
</code></pre>
|
||
|
||
<p>The declaration is correct, but our compiler doesn’t check it. A well-placed <code>declare</code> gives us the compile-time warning back:</p>
|
||
|
||
<pre><code class="language-lisp">(defun place-order (&rest selections)
|
||
(dolist (s selections)
|
||
(declare (type fruit s)) ;; <= declare
|
||
(format t "Ordering ~S~%" s)))
|
||
|
||
(defun placing-orders ()
|
||
(place-order :orange :apple :bacon))
|
||
</code></pre>
|
||
|
||
<p>=></p>
|
||
|
||
<pre><code>The value
|
||
:BACON
|
||
is not of type
|
||
(MEMBER :PEAR :ORANGE :APPLE)
|
||
</code></pre>
|
||
|
||
<p>For portable code, we would add run-time checks with an <code>assert</code>.</p>
|
||
|
||
<h3 id="declaring-class-slots-types">Declaring class slots types</h3>
|
||
|
||
<p>A class slot accepts a <code>:type</code> slot option. It is however generally
|
||
<em>not</em> used to check the type of the initform. SBCL, starting with
|
||
<a href="http://www.sbcl.org/all-news.html#1.5.9">version 1.5.9</a> released on
|
||
november 2019, now gives those warnings, meaning that this:</p>
|
||
|
||
<pre><code class="language-lisp">(defclass foo ()
|
||
((name :type number :initform "17")))
|
||
</code></pre>
|
||
|
||
<p>throws a warning at compile time.</p>
|
||
|
||
<p>Note: see also <a href="https://github.com/fisxoj/sanity-clause">sanity-clause</a>, a data
|
||
serialization/contract library to check slots’ types during
|
||
<code>make-instance</code> (which is not compile time).</p>
|
||
|
||
<h3 id="alternative-type-checking-syntax-defstar-serapeum">Alternative type checking syntax: defstar, serapeum</h3>
|
||
|
||
<p>The <a href="https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#types">Serapeum</a> library provides a shortcut that looks like this:</p>
|
||
|
||
<pre><code class="language-lisp"> (-> mod-fixnum+ (fixnum fixnum) fixnum)
|
||
(defun mod-fixnum+ (x y) ...)
|
||
</code></pre>
|
||
|
||
<p>The <a href="https://github.com/lisp-mirror/defstar">Defstar</a> library provides
|
||
a <code>defun*</code> macro that allows to add the type declarations into the
|
||
lambda list. It looks like this:</p>
|
||
|
||
<pre><code class="language-lisp">(defun* sum ((a real) (b real))
|
||
(+ a b))
|
||
</code></pre>
|
||
|
||
<p>It also allows:</p>
|
||
|
||
<ul>
|
||
<li>to declare the return type, either in the function definition or in its body</li>
|
||
<li>to quickly declare variables that are ignored, with the <code>_</code> placeholder</li>
|
||
<li>to add assertions for each arguments</li>
|
||
<li>to do the same with <code>defmethod</code>, <code>defparameter</code>, <code>defvar</code>, <code>flet</code>, <code>labels</code>, <code>let*</code> and <code>lambda</code>.</li>
|
||
</ul>
|
||
|
||
<h3 id="limitations">Limitations</h3>
|
||
|
||
<p>Complex types involving <code>satisfies</code> are not checked inside a function
|
||
body by default, only at its boundaries. Even if it does a lot, SBCL doesn’t do
|
||
as much as a statically typed language.</p>
|
||
|
||
<p>Consider this example, where we badly increment an integer with a
|
||
string:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (ftype (function () string) bad-adder))
|
||
(defun bad-adder ()
|
||
(let ((res 10))
|
||
(loop for name in '("alice")
|
||
do (incf res name)) ;; <= bad
|
||
(format nil "finally doing sth with ~a" res)))
|
||
</code></pre>
|
||
|
||
<p>Compiling this function doesn’t throw a type warning.</p>
|
||
|
||
<p>However, if we had the problematic line at the function’s boundary
|
||
we’d get the warning:</p>
|
||
|
||
<pre><code class="language-lisp">(defun bad-adder ()
|
||
(let ((res 10))
|
||
(loop for name in '("alice")
|
||
return (incf res name))))
|
||
; in: DEFUN BAD-ADDER
|
||
; (SB-INT:NAMED-LAMBDA BAD-ADDER
|
||
; NIL
|
||
; (BLOCK BAD-ADDER
|
||
; (LET ((RES 10))
|
||
; (LOOP FOR NAME IN *ALL-NAMES* RETURN (INCF RES NAME)))))
|
||
;
|
||
; caught WARNING:
|
||
; Derived type of ("a hairy form" NIL (SETQ RES (+ NAME RES))) is
|
||
; (VALUES (OR NULL NUMBER) &OPTIONAL),
|
||
; conflicting with the declared function return type
|
||
; (VALUES STRING &REST T).
|
||
</code></pre>
|
||
|
||
<p>We could also use a <code>the</code> declaration in the loop body to get a compile-time warning:</p>
|
||
|
||
<pre><code class="language-lisp"> do (incf res (the string name)))
|
||
</code></pre>
|
||
|
||
<p>What can we conclude? This is yet another reason to decompose your
|
||
code into small functions.</p>
|
||
|
||
<h2 id="see-also">See also</h2>
|
||
|
||
<ul>
|
||
<li>the article <a href="https://medium.com/@MartinCracauer/static-type-checking-in-the-programmable-programming-language-lisp-79bb79eb068a">Static type checking in SBCL</a>, by Martin Cracauer</li>
|
||
<li>the article <a href="https://alhassy.github.io/TypedLisp">Typed List, a Primer</a> - let’s explore Lisp’s fine-grained type hierarchy! with a shallow comparison to Haskell.</li>
|
||
<li>the <a href="https://github.com/coalton-lang/coalton/">Coalton</a> library: an
|
||
efficient, statically typed functional programming language that
|
||
supercharges Common Lisp. It is as an embedded DSL in Lisp that
|
||
resembles Haskell or Standard ML, but lets you seamlessly
|
||
interoperate with non-statically-typed Lisp code (and vice versa).</li>
|
||
<li><a href="https://dev.to/vindarel/compile-time-exhaustiveness-checking-in-common-lisp-with-serapeum-5c5i">exhaustiveness type checking at compile-time</a> with <a href="https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#ecase-of-type-x-body-body">Serapeum</a> for enum types and union types (ecase-of, etypecase-of).</li>
|
||
</ul>
|
||
|
||
<hr />
|
||
|
||
<div class="footnotes" role="doc-endnotes">
|
||
<ol>
|
||
<li id="fn:1" role="doc-endnote">
|
||
<p>The term <em>object</em> here has nothing to do with Object-Oriented or so. It
|
||
means “any Lisp datum”. <a href="type.html#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
|
||
|
||
<p class="page-source">
|
||
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/type.md">type.md</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
|
||
// Don't write the TOC on the index.
|
||
if (window.location.pathname != "/cl-cookbook/") {
|
||
$("#toc").toc({
|
||
content: "#content", // will ignore the first h1 with the site+page title.
|
||
headings: "h1,h2,h3,h4"});
|
||
}
|
||
|
||
$("#two-cols + ul").css({
|
||
"column-count": "2",
|
||
});
|
||
$("#contributors + ul").css({
|
||
"column-count": "4",
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
<div>
|
||
<footer class="footer">
|
||
<hr/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">our contributor's Common Lisp video course on Udemy</a>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div id="toc-btn">T<br>O<br>C</div>
|
||
</div>
|
||
|
||
<script text="javascript">
|
||
HighlightLisp.highlight_auto({className: null});
|
||
</script>
|
||
|
||
<script type="text/javascript">
|
||
function duckSearch() {
|
||
var searchField = document.getElementById("searchField");
|
||
if (searchField && searchField.value) {
|
||
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
||
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
||
// https://duckduckgo.com/params
|
||
// kj=b2: blue header in results page
|
||
// kf=-1: no favicons
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
||
|
||
</body>
|
||
</html>
|