{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Common Lisp, Typing, and Mathematics\n",
"\n",
"By [*Francis Sergeraert*](https://www-fourier.ujf-grenoble.fr/~sergerar/Papers/Ezcaray.pdf), August 2001\n",
"(Adapted for [`cl-jupyter`](https://github.com/fredokun/cl-jupyter) by Gerd Heber, February 2017)\n",
"\n",
"## Introduction.\n",
"\n",
"Common Lisp is seldom used by mathematicians and this is an anomaly. The high level of quality of Common Lisp comes from a simple reason: Common Lisp is a *mathematical* object, certainly one of the most beautiful and *potentially* productive existing at this time. Common Lisp is a descendant of the fantastic $\\lambda$-calculus, designed by Church to produce one of the deepest *mathematical* results of the last century, namely the negative answer to Hilbert's Entscheidungsproblem (\"decision problem\"): no algorithm can determine whether an arbitrary mathematical statement is true, false or undecidable.\n",
"\n",
"Then, by a quite indirect route, $\\lambda$-calculus became a fascinating programming language, Lisp. Lisp's birth is old, at the end of the fifties, and forty years later it is clear it remains the most advanced \"common\" programming language, \"common\" meaning *with an ANSI norm*, in this case the $\\ldots$ Common Lisp norm. The Common Lisp language is so advanced that it is not so easy to use it for serious applications: a reasonable lucidity about its complex and far-reaching structure is required; so that the initiation stage in the learning process of this language is a little hard. The present paper is a tutorial to introduce *mathematicians* to the main components of Object Oriented Programming in Common Lisp. The extraordinary work of Guy Steele and his X3J13 committee during the years 1980-1994 led to a powerful language, on one hand, but on the other hand taking seriously account of standard *practical constraints* in *actual* programming work. Common Lisp is now widely used for complex industrial applications, mainly because of the powerful *dynamic* tools that are available; but it is not used very much for scientific applications, in particular for mathematical applications, and this must be corrected.\n",
"\n",
"No *concrete* language can be completely formalized, but the mathematical precision of the definition of Common Lisp gives its user much security, the precision which is required for sophisticated mathematical applications, in particular when complicated data-sharing arrangements are necessary. Another aspect of Common Lisp is without any equivalent with the other languages: the user may *freely* design his application at the best programming level, sometimes at a very high level, directly handling rich and complex mathematical structures; for example it is explained in [Section 5.2](#Kenzo-classes.) that three simple lines of Lisp code are sufficient to explain to the machine that a simplicial group is simultaneously a Kan space and a Hopf algebra, sharing a common graded differential coalgebra structure, but with some added data, namely two simplicial morphisms describing the simplicial group structure. Other times, in the *same* program, you may on the contrary work at a very low level, in an assembler-like style. The language is carefully *stratified* to help the developer to remain lucid and at ease about these questions. The Common Lisp *macro generator*, without any equivalent in other common languages, allows also the programmer to easily use his low level code when he is on the contrary working at a high level.\n",
"\n",
"Which is a good element for appreciation is the ability of a language to be quickly adapted to new *mandatory* evolutions; at this time, Object Oriented Programming (OOP) must be integrated in a language claimed *common*, and Common Lisp succeeded in doing it so nicely that it can even be used to *describe* what OOP exactly is. The CLOS (Common Lisp Object\n",
"System) chapter of [[STEL2]](#STEL2) is one of the most amazing parts of Computer Science. The general *dynamic* feature of Common Lisp has in particular been kept: the user may dynamically change the class of his objects; he may also redefine dynamically the classes without loosing the objects of this class; and the user may freely decide exactly what happens for these objects. If this is not yet flexible enough, the Metaobject Protocol (MOP), not yet ormalized, but already widely used, allows our user to freely define his own OP system; CLOS is in fact only a particular application of MOP, see [[KCRB]](#KCRB).\n",
"\n",
"This paper is devoted to a few didactical examples to help mathematicians to understand how Common Lisp and more precisely CLOS can be used for mathematical applications. It is organized as follows. Firstly, a small set of artificial examples is used to explain the general structure of CLOS. The main work in OOP, and certainly the most difficult one, is at the initialization stage of the instances (objects). Even if a user does not intend to use the current Common-Lisp, studying how the initialization process is designed in this environment will explain to him the good points of view in this domain.\n",
"\n",
"Then a more significant example is described to show the actual workstyle when CLOS is used in a non-beginner job. Because the OOP subject is strongly related to *typing*, a possible description of what typing *could be* is used as a theme of an exercise showing how CLOS allows to implement the consequences of this description. This example is very simple and should be a good tutorial to simultaneously understand the initial points of OOP under Common Lisp and what typing is, at least from the point of view of the *actual* programmer.\n",
"\n",
"The next section is more mathematically oriented: it is explained how the basic mathematical *categories*, sets, magmas and monoids, can be directly implemented in CLOS, without meeting any kind of difficulty. The possibility for the Lisp user to arbitrarily mix OOP and high level functional programming (lexical closures) gives at once the required tools. The specialized programming environments such as Axiom, Gap, Magma $\\ldots$ so can be skipped and the mathematician may freely work at an arbitrary level *in the programming language itself*, keeping a perfect control of the environment, without being bothered by a restrictive environment, seldom well-adjusted to an arbitrary new *research* work.\n",
"\n",
"It was the case of the author and his colleagues when the central problems of Algebraic Topology were considered from a computational point of view [[SRGR3]](#SRGR3)[[RBSR6]](#RBSR6). The resulting Lisp program Kenzo [[DRSS]](#DRSS) shows a concrete example of use of CLOS for a relatively large implementation work[1](#footnote_1). The Kenzo program is not at all didactic, it is an actual program able to produce mathematical results that are unreachable otherwise. Some points of the experience acquired by Kenzo's authors are discussed in the last section and in this way the reader will see a few more concrete points of CLOS.\n",
"\n",
" 16000 Common Lisp lines and a 340pp documentation.\n",
"\n",
"## CLOS (= Common Lisp Object System).\n",
"\n",
"Strictly speaking, the notion of *Common Lisp Object System*, CLOS in short, does not make sense anymore. The first definition of Common Lisp, known as Common-Lisp-1984 [[STEL1]](#STEL1), did not have any \"object system\". Typing was already strong in CL-1984, at least if wished by the user; standard types were defined and the initial type system was freely extendable by the user, but the now classical notions of *classes*, *instances*, *generic functions* and *methods* were not yet available. The pre-ANSI version of Common Lisp, namely Common-Lisp-1990, contained a *proposal* for an OOP system, called Common Lisp Object System [Ch. 28, pp 770-864 [STEL2]](#STEL2); the pre-versions of this object system were already widely used at this time by the Lisp programmers. Finally, the ANSI specification [[ANSI]](#ANSI) of Common Lisp (1994) completely integrated the so-called Common Lisp Object System in the very definition of the language. The standard Lisp programmer is now assumed working according to the spirit of OOP, constantly using classes, instances of classes, methods and so on, like in C++ or Java[2](#footnote_2). But the perfect integration of the general ideas of OOP with the so powerful Common Lisp gives its user many capabilities which are at this time outside of scope with other languages, mainly when *functional programming* is involved. However we continue to call CLOS the part of the ANSI Common Lisp language devoted to OOP.\n",
"\n",
"This object system in Common Lisp coexists with the old typing system and also the old *structure* system, which allowed the user to define, construct and use the classical *record* objects, with several fields, typed or not. From a hierarchical point of view, the typing system and the structure system are *subsystems* of the object system: special predefined classes correspond to the main classical data types, and the same for the structured objects. But normaly the current Lisp programmer mainly works with the object system, organizing his workspace in classes, subclasses, methods and so on.\n",
"\n",
"The Common Lisp object system is elegant and powerful. As usual in Common Lisp, it is organized so that the user keeps a very large freedom. A *standard style* is carefully designed, but the user may go far from this standard style if special situations are encountered.\n",
"\n",
" With a slightly different terminology.\n",
"\n",
"### A quick comparison with C++ and Java.\n",
"\n",
"A few indications are given in this section about the main OOP features of Java, C++ and CLOS. In Java, the classes are \"above\" the methods (member functions): the specific functions for a class are defined *inside this class* so that the class and member function hierarchies are more or less *parallel*. This leads sometimes to artificial classes, typically the `System` class and the `Math` class, when this *rule* becomes non-sense in particular situations. The *member functions* are available under C++ too.\n",
"\n",
"In C++ you may also *overload* the functions, in particular the member functions. There are no member functions in CLOS[3](#footnote_3), there is only an overloading feature. But three other features are provided by CLOS which make particularly convenient and elegant the developer work:\n",
"\n",
"- The initialization process of the objects is essential in OOP; the general CLOS organisation of this work is really wonderful, in particular conceptually very simple, leading the programmer towards the good points of view, see [Section 2.3](#Initializing-instances.) for a small introduction to this point.\n",
"- The *method qualifiers* give much flexibility to organize the work of all the *applicable* methods; numerous simple examples in this paper.\n",
"- The functional basis of Common Lisp allows the user to easily install *functional slots* (members) of any sort, in particular *compiled closure slots*, dramatically extending the programming scope; [Section 4](#CLOS-and-Mathematical-Structures.) gives striking applications of this feature; in particular the instances may be themselves *funcallable*, in other words the instances become also functional objects, see [Section 3](#What-typing-is.) for a typical illustration.\n",
"\n",
"In CLOS, the *basic ingredients* are clearly distinguished:\n",
"\n",
"- The *class system*, mainly used through the `defclass` statement, allows the user to define the structure of his *instances* (objects), in particular through the notion of *subclass* (derived class).\n",
"- The *function type* is *primitive*; a function is an object which can be *funcalled* (called) for some work about some *arguments*.\n",
"- The *packaging system* allows the developer to precisely define what parts of his source code are normally directly known and/or reachable by a user of his program, obtaining in particular the equivalent of the `private` C++ code.\n",
"\n",
"Then, if the *class system* and the *functional* organization of Common Lisp are to be simultaneously considered, the particular notions of *generic functions* and *methods* must be used.\n",
"\n",
"- A *generic function* is a functional object the exact work of which will depend on the class of its arguments.\n",
"- The code for *some* specific work of a generic function corresponding to *some* particular class distribution of its arguments and *some qualifier* is a *method* object; each method is defined by a `defmethod` statement.\n",
"- In general *several* methods are involved when a *generic function* is invoked, they are called the *applicable* methods. CLOS gives its user a large freedom to organize these methods with respect to each other, to cover any possible situation, in particular when a sophisticated chronology of their work is required. This feature has no equivalent in C++ or Java, this is obtained by the *method qualifiers*.\n",
"\n",
"In this way, the CLOS *methods* can be thought of arranged in a *three*-dimensional array where each entry is associated to a generic function, the first index, the class distribution of the parameters, the second index, and the method qualifier, the third index.\n",
"\n",
"The CLOS method qualifiers have no equivalent in C++ or Java; a predefined qualifier organization is provided, already rather rich, sufficient for most of the ordinary cases[4](#footnote_4). If still more sophisticated combinations of methods are required, the `define-method-combination` function allows the developer to freely extend this organization.\n",
"\n",
"For example, and this will be detailed in [Section 4](#CLOS-and-Mathematical-Structures.), a mathematician who installs under CLOS the traditional mathematical categories must organize his work as follows:\n",
"\n",
"- The `defclass` statements will be used to define what a *set* object is, what a *group* object is, what a *chain complex* object is, and so on.\n",
"- The `defgeneric` statements will be used to define functions working on these objects, but these \"functions\" are traditionnally called in this case *functors* in mathematics; therefore one `defgeneric` statement for the *sum* functor, another *defgeneric* for the *product* functor, another `defgeneric` for the *classifying space* functor, and so on.\n",
"- Finally each generic function will have various methods to adapt the generic function to specific cases; for example the product of two objects of some category is also an object of this category with the corresponding structure to be defined. Therefore one product method for the *sets*, another product method for the *magmas*, another product method for the *monoids*, and so on. The `call-next-method` and `change-class` functions will allow these methods to *possibly* refer to the *less specific* ones.\n",
"\n",
" A C++ or Java member function is nothing but a CLOS generic function where the first argument is given *before* the function reference, with an implicit `with-slots` for this particular argument; such a feature can be easily added to the environment with the Metaobject Protocol; in general CLOS prefers to keep the maximal symmetry between the critical arguments.\n",
"\n",
" For example quite sufficient for the Kenzo program.\n",
"\n",
"### Very simple CLOS examples.\n",
"\n",
"Classes can be defined with the traditional hierarchy and methods may be defined which will be called, or not, according to the argument classes. The general `call-next-method` function allows the user to invoke shadowed methods.\n",
"\n",
"Let us define[5](#footnote_5) a simple class `C1` and a subclass `C2`.\n",
"\n",
" In the original paper, the small Lisp examples showed for illustration have really been run under Allegro Common Lisp and may be repeated under any ANSI Common Lisp; the Lisp prompt is here '`>`'; the maltese cross $\\maltese$ corresponds to the `` key asking for the evaluation of the typed-in statement; usually the symbols are keyed in lower case, and Lisp displays the answers with upper case."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS C1 ()\n",
" ((sl1 :initarg :sl1 :reader sl1)))"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS C2 (C1)\n",
" ((sl2 :initarg :sl2 :reader sl2)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `C2` class is a *subclass* of the `C1` class. A `C1`-object, in Lisp you must say a `C1`-*instance*, has only\n",
"one component, you must say in Lisp one *slot*, labelled `sl1`, and a `C2`-instance has one further slot labelled `sl2`. Because of the `:initarg` slot-options, these slots may be initialized through the keyword arguments `:sl1` and `:sl2`. We create a `C1`-instance and a `C2`-instance."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"I1"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar i1 (make-instance 'c1 :sl1 1))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"I2"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar i2 (make-instance 'c2 :sl1 11 :sl2 22))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You see, and this is the *default display*, only the class of the instance and its machine address are displayed. We would like to display our simple instances with their slots[6](#footnote_6). In general every object is displayed through the generic function `print-object`, and the user is advised to write specific *methods* for this generic function to obtain the desired display. Just to illustrate the general organisation of methods in CLOS, we\n",
"will define three `print-object` methods to \"print\" a `C1`- or `C2`-instance, to show the `sl1` and possibly the `sl2` slot(s). The `C2`-method calls the `C1`-method through `call-next-method`, and the `:after` method allows to print the '`>`' terminal.\n",
"\n",
" This is not standard: frequently the slots are numerous and complicated, and it is not sensible to display the instances with all their slots."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT ((c1 c1) stream)\n",
" (declare (type stream stream))\n",
" (format stream \"#<~S sl1=~S\" (class-name (class-of c1)) (sl1 c1))\n",
" c1)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT :after ((c1 c1) stream)\n",
" (declare (type stream stream))\n",
" (format stream \">\"))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT ((c2 c2) stream)\n",
" (declare (type stream stream))\n",
" (call-next-method)\n",
" (format stream \" sl2=~S\" (sl2 c2))\n",
" c2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `format` statement is analogous to the traditional `printf` of C. Frequently[7](#footnote_7), Lisp objects are displayed as strings which begin with '`#<`'. Now we redisplay our existing `C1`- and `C2`-instances.\n",
"\n",
" In principle a *non-readable* display must begin with '`#<`', but in this case, because of the complete display, in fact it could be read ..."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(# #)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(list i1 i2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can play to change the class of these instances."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class i1 'c2 :sl2 2)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class i2 'c1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You see CLOS has taken the most natural decisions, but we will explain later how the process can be freely customized by the programmer.\n",
"\n",
"There is also the possibility of `:around` methods, encapsulating the so-called *primary* ones:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT :around ((c1 c1) stream)\n",
" (declare (type stream stream))\n",
" (format stream \"*\")\n",
" (call-next-method)\n",
" (format stream \"*\")\n",
" c1)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT :around ((c2 c2) stream)\n",
" (declare (type stream stream))\n",
" (format stream \"+\")\n",
" (call-next-method)\n",
" (format stream \"+\")\n",
" c2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we see the role of these methods:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(+*#*+ *#*)"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(list i1 i2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Again it is interesting to deduce from this example the chronology of the calls of the five user-defined `print-object` methods. The standard organization allows the user to define *primary* and *auxiliary* methods, the last ones being *qualified* by the keyword `:before`, `:after` or `:around`; the methods are *specialized* by giving arbitrary combination of classes of the arguments; here, only the first argument was specialized, but we will see later natural situations where\n",
"several arguments are specialized. Simple but mathematically coherent rules, in particular based on topological sorting, explain what methods work in a particular case and in what order. Furthermore these possibilities may be freely modified or extended by the user through the `define-method-combination` function.\n",
"\n",
"### Initializing instances.\n",
"\n",
"It is well known the main part and the main difficulties of the OOP job are in the initialization work for the instances. Let us examine the general organization of the CLOS initialization process.\n",
"\n",
"The essential components of the initialization process in CLOS are the following generic functions:\n",
"\n",
"- The *standard* `make-instance` allocates the memory space for the instance to be created and calls `initialize-instance` to initialize it;\n",
"- The *standard* `initialize-instance` in fact calls `shared-initialize` to do the initialization work.\n",
"\n",
"The role of `shared-initialize` is the following. Frequently the initialization work to be done is essentially the same when an instance is created, or *re*initialized, or when its class is changed, or finally when the underlying class is itself redefined. If possible, the user is advised to define this common work in `shared-initialize` methods. If on the contrary there are significant differences between these cases, the user may write particular methods for the following generic functions:\n",
"\n",
"\n",
"\n",
"- `initialize-instance`: called by `make-instance` to initialize a just created instance; the user can extend and/or modify the standard initialization by writing specific methods for some classes.\n",
"- `reinitialize-instance`: called by the user when he wants to \"refresh\" some instance; sometimes it is the same as initializing from scratch, sometimes not; in the first case it is better to write an appropriate `shared-initialize` method; in the second case, the user must write specific methods for `reinitialize-instance`.\n",
"- `change-class`: called by the user when the class of an instance is *changed*; this function asks for a kind of conversion. Normally this function calls the generic function `update-instance-for-different-class` and the user may write methods for the latter. A simple example follows.\n",
"- `defclass`: if a `defclass` is in fact a *redefinition* of the class, each time an instance of the old version is considered, the generic function `update-` `instance-for-redefined-class` is applied, allowing in principle to coherently continue the work.\n",
"- `make-instance`: the user can also entirely redefine the creation and initialization process.\n",
"\n",
"Writing appropriate methods for these generic functions, the user can freely define the initialization work, clearly distinguishing the various levels from each other; if sufficient, the programmer may partially or totally use the *standard style* without any further work. You understand that combining these methods with the user-defined class hierarchy, combining also with the various possibilities of *auxiliary methods* (`:before`, `:after`, `:around`), the user has a fantastically large workspace. However, once this structure is reasonably understood, it is not difficult to be used, and quickly very powerful.\n",
"\n",
"### The example of `change-class`.\n",
"\n",
"The various situations that are met in the artificial simple example of this section around the `change-class` generic function allow to easily understand the general CLOS organisation of the initialization work."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS E12 ()\n",
" ((sl1 :initarg :sl1 :reader sl1)\n",
" (sl2 :initarg :sl2 :reader sl2)))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS E13 ()\n",
" ((sl1 :initarg :sl1 :reader sl1)\n",
" (sl3 :initarg :sl3 :reader sl3)))"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"INS"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar ins (make-instance 'e12 :sl1 1 :sl2 2))"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class ins 'e13)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ins"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(sl1 ins)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"UNBOUND-SLOT: \n",
" #\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(sl3 ins)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ther is no relation at all between both classes `E12` and `E13`; there is a common slot *name*, namely `sl1`, but this does not imply any relation between both classes. With an exception, if ever the class of an instance is changed from `E12` to `E13`, this common slot name will be considered by the standard `change-class` which in fact calls the standard `update-instance-for-different-class`: this method transmits the slots with the same *names* in both classes, gives up the other slots in the source instance, and lets unbound the other slots of the target instance. Here, the `sl1` slot has been kept, the `sl2` slot has been given up, and the `sl3` slot in the result is let unbound, which implies the error when this slot is read by the `:reader` method `sl3`. Note also the pseudo-new instance is at the same machine address, but the small\n",
"invisible steps of the garbage-collector continually modify the actual addresses, which is made obvious by the value of the symbol `ins` locating our *unique* but *variable* instance.\n",
"\n",
"Let us convert again our instance, but with an additional keyword argument."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class ins 'e12 :sl2 22)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"22"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(sl2 ins)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The default methods are sufficient to pass initial arguments to `change-class` to obtain partial initializations of the new *version* of the *same* instance. Let us consider now the situation where for some strange \"theoretical\" reason, when the class is changed from `E12` to `E13`, the value of the `sl3` slot must be computed as the product of the `sl2` slot of the source instance multiplied by some number included in the `change-class` data. This is obtained as follows."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD UPDATE-INSTANCE-FOR-DIFFERENT-CLASS :after\n",
" ((source e12) (target e13) &key (multiplier 1))\n",
" (declare (type number multiplier))\n",
" (setf (slot-value target 'sl3)\n",
" (* (sl2 source) multiplier)))"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class ins 'e13 :multiplier 4)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(1 88)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(list (sl1 ins) (sl3 ins))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Because we have used an `:after` method, the standard *primary* method is firstly called, solving the `sl1` transfer. And after this is done, our auxiliary method works. Note in particular the elegant handling of the extra multiplier argument; because this keyword argument is used in our method, it becomes available for the user when a `change-class` `E12` $\\rightarrow$ `E13` happens.\n",
"\n",
"We want finally to consider the situation where a conversion `E13` $\\rightarrow$ `E12` must be installed, where the new version of the instance must have both slots redefined to zero. In this case the old version of the instance is useless, so that we can directly obtain this conversion by a `change-class` method."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD CHANGE-CLASS :after ((ins e13) (class (eql 'e12)) &rest rest)\n",
" (declare (ignore rest))\n",
" (setf (slot-value ins 'sl1) 0\n",
" (slot-value ins 'sl2) 0))"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class ins 'e12)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(0 0)"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(list (sl1 ins) (sl2 ins))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## What typing is.\n",
"\n",
"The small examples of the previous section were artificial, and we want to consider now a *plausible* situation. We want to use CLOS to implement a simple coherent *typing system*, allowing the user to simultaneously use arbitrary types and high levels of *functional programming*, both subjects being furthermore strongly dependent on each other.\n",
"\n",
"What is typing? If a *mathematical* definition of typing is wished, many definitions are possible, and one of them is to be illustrated by a small CLOS program. Note this *new* typing system will be installed while the standard one remains active in our environment.\n",
"\n",
"Typing consists in giving the programmer the ability of *partial* descriptions of his algorithms. Let us call $\\mathcal{A}$ the *universe* of all the machine objects, $\\mathcal{A}$ for anything. Any algorithm can be viewed as a map $\\mathcal{A}\\rightarrow\\mathcal{A}$ *not everywhere defined*. Typically the Euclid algorithm is defined on the set $\\mathbb{N}_* \\times \\mathbb{N}_*$ of pairs of positive integers, and returns such an integer. The set $\\mathbb{N}_*$ is a subset of $\\mathcal{A}$, and the latter contains also the set $\\mathcal{L}$ of lists of any length; among these lists, some of them are made of two positive integers; let us call $\\mathcal{L}(\\mathbb{N}_* , \\mathbb{N}_*)$ the corresponding subtype. The type specification (signature) for the Euclid algorithm $E$ is then $E: \\mathcal{L}(\\mathbb{N}_* , \\mathbb{N}_*) \\rightarrow \\mathbb{N}_*$.\n",
"\n",
"Speaking so, we have used a few subsets of $\\mathcal{A}$. Such a simple example could create some wrong ideas. On one hand, one could think two different types should be disjoint, but it is not the case for $\\mathcal{L}$ and $\\mathcal{L}(\\mathbb{N}_* , \\mathbb{N}_*)$. If not disjoint, one could then require one of the considered types is included in the\n",
"other one; for example $\\mathcal{L}(\\mathbb{N}_* , \\mathbb{N}_*) \\subset \\mathcal{L}$ and it seems sensible to organize the types as a large *oriented graph* describing more and more finely the various sets of objects the user works with. Simple typing systems usually are of this sort.\n",
"\n",
"Another idea has a much larger scope; it consists in deciding a type is nothing but some subset of $\\mathcal{A}$, where the *membership property* may be verified by an algorithm. In other words we start only with two predefined types, $\\mathcal{A}$ and $\\mathcal{B}$; the second one, the *Boolean* type, has only two objects, $\\top$ and $\\bot$, implemented in Lisp as the symbols `T` and `NIL`. It is then interesting to define a type as an algorithm $\\mathcal{A}\\rightarrow\\mathcal{B}$ *everywhere* defined; in other words, the algorithm defining a type has the special signature $\\mathcal{A}\\rightarrow\\mathcal{B}$.\n",
"\n",
"This is sufficient in simple programming, but fails as soon as functional programming must be considered. In fact we meet the Russell paradox or if you prefer the Gödel theorem. Let us call $\\mathcal{T}$ the type of ... types, that is the set of functional objects $\\alpha:\\mathcal{A}\\rightarrow\\mathcal{B}$. Then no algorithm can verify the membership of $\\mathcal{T}$! Let us assume $\\tau$ is such an algorithm; therefore $\\tau:\\mathcal{A}\\rightarrow\\mathcal{B}$ is everywhere defined and $\\tau(\\alpha) = \\top$ if and only if $\\alpha$ defines a type, that is, $\\alpha:\\mathcal{A}\\rightarrow\\mathcal{B}$ is also everywhere defined. Then one could design the subtype $\\mathcal{T}'$ made of the algorithms $\\alpha \\in \\mathcal{T}$ such that $\\alpha(\\alpha) = \\bot$, in other words the type of \"typing algorithms\" $\\alpha$ that are *not* \"element\" of the type associated with $\\alpha$. It would be easy to write down the algorithm $\\tau'$ corresponding to the new type $\\mathcal{T}'$:\n",
"\n",
"$$\n",
"\\begin{array}{rcll}\n",
" \\tau'(\\alpha) &=& \\mathtt{if}\\ \\tau(\\alpha) &\\mathtt{then}\\\n",
" \\mathtt{not}\\ \\alpha(\\alpha)\\\\\n",
" & & & \\mathtt{else}\\ \\bot\n",
"\\end{array}\n",
"$$\n",
"\n",
"The algorithm $\\tau'$ firstly examines whether its argument $\\alpha$ is an algorithm $\\mathcal{A}\\rightarrow\\mathcal{B}$; if yes, the algorithm $\\alpha$ may work on any object, in particular on itself, and $\\tau'$ returns the opposite of $\\alpha(\\alpha)$; otherwise the answer is negative. Once the algorithm $\\tau:\\mathcal{A}\\rightarrow\\mathcal{B}$ is available, then the object $\\tau':\\mathcal{A}\\rightarrow\\mathcal{B}$ is available too, but Cantor and Russell remarked there is no possible answer for $\\tau'(\\tau')$: the answer of $\\tau(\\tau')$ should certainly be $\\top$, so that we obtain the contradictory relation $\\tau'(\\tau') = \\mathtt{not}\\ \\tau'(\\tau')$. Therefore the algorithm $\\tau$ may not exist.\n",
"\n",
"The traditional (pseudo-) solution for this difficult problem consists in *delaying* the examination of the type of functional objects. If an object $\\alpha$ is claimed to be an algorithm $\\mathcal{T}_1\\rightarrow\\mathcal{T}_2$, this\n",
"cannot be verified by a *general* algorithm; instead, we must *wait* for an actual run of the algorithm $\\alpha$: if the computation of $\\alpha(\\omega)$ is asked for, some process can *then* verify the argument $\\omega$ is really in the type $\\mathcal{T}_1$; if yes the computation of $\\alpha(\\omega)$ is started and the result $\\omega'$ is examined in turn, verifying the relation $\\omega' \\in\\mathcal{T}_2$. In other words it is not possible to verify the type of a functional object *in general*; the only possible verification consists, each time the algorithm works, in verifying that the argument and the result have the correct type; this \"verification\" is not at all complete, it can be applied only to a finite number of calls of the algorithm $\\alpha$.\n",
"\n",
"This organization must be recursive: the source and/or target types could in turn be functional, so that the verifications $\\omega \\in \\mathcal{T}_1$ and $\\omega' \\in \\mathcal{T}_2$ maybe must also be \"delayed\" with the meaning just explained. In particular if $\\omega$ and $\\omega'$ are both functional, then the functional object $\\omega$ *will* probably *be* used when the object $\\omega'$ will work. It is only at this time the correctness of the claimed types for $\\omega$, at least for this particular use, may be verified. Examples of this sort are showed in this paper.\n",
"\n",
"In this way, when a whole program is executed, all the *particular uses* of the functional objects imply that the type of arguments and result are verified, so that when the program is finished, for all the invocations of functional objects, types of input and output are confirmed. Thinking a little about this situation finally leads to the following conclusion: as far as they have been used, the type rules about functional objects have been satisfied *for this specific run*. A quite satisfactory conclusion.\n",
"\n",
"To illustrate the Common Lisp object system, we take as exercise subject the implementation of such an organization in Common Lisp, using the main components of CLOS.\n",
"\n",
"### Classes as structured types.\n",
"\n",
"A *class* is firstly a structured type. Each *instance* (element) of a *standard* class has several *slots* (components, fields, members), and each slot has various properties described in the definition of the class. The *builtin* classes, corresponding to old classical types (integers, symbols, ...), are not standard.\n",
"\n",
"The classes are organized in a hierarchical way: a class may be or not a *subclass* of another one, and this defines an *order* between the defined classes. This order must be coherent but in general it is not *total*, that is, for two classes $\\mathcal{C}_1$ and $\\mathcal{C}_2$, they may or may not be compared. But any class is a subclass of the maximal type~$\\mathcal{A}$, which contains any object, in particular the corresponding class object itself; to prove this point, we assign this class object to the symbol `universe` and verify the object so located is of the type so described; this maximal class $\\mathcal{A}$ corresponds to the set (type) $\\mathcal{A}$ of the previous section."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"UNIVERSE"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar universe (find-class 't))"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(typep universe universe)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The *types* of the proposed organization for typing in this paper are pointed out by the letters `TP` only. This is necessary because we have to simultaneously work with three typing systems:\n",
"\n",
"1. The old one of Common Lisp, still present in our workspace; we call it the *type-system*;\n",
"2. The *class-system* which is the main constituent of CLOS;\n",
"3. The theoretical proposal of our exercise; it is called the `TP`*-system* in this text.\n",
"\n",
"> In the following, we use a metaobject protocol for CLOS which is not part of the ANSI Common Lisp standard. The SBCL compiler implements this protocol in the `sb-mop` package."
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(use-package :sb-mop)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The `TP`-class.\n",
"\n",
"Each `TP`-type is described by an instance of the `TP` class now to be defined. We define this class as follows:"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS TP ()\n",
" ((name :type symbol :initarg :name :initform (gensym) :reader name))\n",
" (:metaclass funcallable-standard-class))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"At this time, only one slot called `name` is defined for an instance of `TP`. Four *slot-options* have been used with the following meanings:\n",
"\n",
"- The `:type` option explains the `name` slot must contain a `symbol`;\n",
"- The `:initarg` option says the `name` slot may be initialized through the keyword `:name`;\n",
"- The `:initform` option says that if the `name` slot is not otherwise initialized, it must be automatically initialized by the `gensym` function, a predefined Lisp function which creates from scratch a certainly *new* symbol;\n",
"- Finally the `:reader` option asks CLOS to construct a *method* named `name` allowing the user to call this method to obtain the value of the slot.\n",
"\n",
"The `:metaclass` class option is explained later.\n",
"\n",
"A *type descriptor* of the `TP` system is always an instance of the `TP`-class. The corresponding type is functional or not; if functional the type descriptor is in fact an instance of the `FTP` class, a subclass of the `TP` class, else the type descriptor is an instance of the `DTP` class (discriminant type), another subclass of the `TP` class. The situation is here particularly simple: a type descriptor is *always* a `TP` instance, and in fact always an instance of only one subclass, either `FTP`, or `DTP`. Much more complicated situations between classes and subclasses can be designed under CLOS, but this simple situation allows us to show the main points of the nature of CLOS. In other words, the class diagram is this one.\n",
"\n",
"\n",
"\n",
"The type descriptor is firstly some `TP` instance, but it is convenient in this situation to locate the descriptor through a symbol and this is the reason of the `name` slot: this slot contains the symbol which in principle locates the `TP`-type descriptor. So that we have two (almost) \"symmetric\" pointers: the symbol points to the descriptor and the `name` slot of this descriptor points to this symbol; this is nothing but an *explicit* C++-`this` method. If ever the user is not really interested in the symbol, the `gensym` will automatically generate a symbol for completeness.\n",
"\n",
"\n",
"\n",
"The method `name` may now be applied to a `TP`-descriptor to obtain the associate symbol.\n",
"\n",
"We have explained in the previous section the role of the `print-object` generic function. Here, our strategy consists in simply locating `TP`-types through symbols to be considered as *labels*, so that it is natural to display such a type by the associate symbol."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT ((tp tp) stream)\n",
" (format stream\n",
" \"#<~S ~S>\"\n",
" (class-name (class-of tp)) (name tp)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What is finally displayed through the (implicit) call of this method could be for example `#`; it is a tradition in Lisp to begin the display of a *non-readable* object by '`#<`'; this notion is carefully defined in ANSI Common-Lisp, but it is not the subject of this text. The value of `(name tp)` is the associate *name* of the\n",
"type-descriptor; the value of `(class-of tp)` is the `class` of `tp`, therefore the *class-object* `DTP` or `FTP`, and\n",
"the `class-name` is the symbol naming this class. We do not want to explain here the technical details of the `format` Lisp function, which is similar to the traditional `printf` C function, but fantastically more flexible.\n",
"\n",
"A little earlier the symmetry property between a `TP`-type descriptor and the symbol locating it was explained. It is a little painful for the user to manage the necessary pointers, but an appropriate method can be used to automate the process during the initialization stage."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD INITIALIZE-INSTANCE :after ((tp tp) &rest rest)\n",
" (declare (ignore rest))\n",
" (set (name tp) tp))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `:after` *qualified* method shown above means that *after* the standard initialization process, something must be done: the symbol in the `name` slot must be bound to the `TP`-instance itself.\n",
"\n",
"### The `DTP`-class.\n",
"\n",
"We define now the subclass `DTP` (discriminant type) of the class `TP`."
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS DTP (TP)\n",
" ((sub :type list :accessor sub)\n",
" (sup :type list :accessor sup))\n",
" (:metaclass funcallable-standard-class))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the first line, the pseudo-argument `TP` indicates the new class is a subclass of the class `TP`. Two new slots are defined, so that a `DTP`-instance will have three slots because of the `name` slot already defined for a `TP`-instance:\n",
"\n",
"- The `sub` slot (sub-types) is a list of the types that are known equal or smaller than the one which is described by the `DTP`-instance.\n",
"- The `sup` slot (super-types) is a list of the types that are known equal or larger than the one which is described by the `DTP`-instance.\n",
"\n",
"Note that `:accessor` methods (`sub` and `sup`) have been required for the corresponding slots; this means these methods will allow to *read* the corresponding slot but they can also be used to *write* it or *update* it. In fact, these slots must in general be modified after the creation, to maintain the coherence of the `TP`-system.\n",
"\n",
"We create now the minimal and the maximal type objects of the `TP`-system, namely the `tp-void` and `tp-any` types. A special initialization work is done here because the general initialization process will assume these types are *already* defined in the environment."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-VOID"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFVAR tp-void (MAKE-INSTANCE 'DTP :name 'tp-void))"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(TP-VOID TP-ANY)"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(SETF (sub tp-void) '(tp-void)\n",
" (sup tp-void) '(tp-void tp-any))"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(SET-FUNCALLABLE-INSTANCE-FUNCTION tp-void\n",
" #'(lambda (obj)\n",
" (declare (type t obj) (ignore obj))\n",
" (the boolean nil)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Firstly the `DTP`-instance to be assigned to the `tp-void` symbol is created by the call of the generic function `make-instance`, then the `sub` and `sup` slots are defined, and finally, because of the `:metaclass` class option, it is possible to associate a functional object to this instance, namely the function which always returns `nil`; this is nothing but the characteristic function of the void type in our environment. Example of use of this function:"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-void 'anything)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Because of the `funcall`, the functional object associate to the object pointed by the symbol `tp-void` is called with the symbol `anything` as argument. Whatever is the argument, the answer is `nil`. We can verify the pointer symmetry between the `name` slot and the corresponding symbol:"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tp-void"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-VOID"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(name tp-void)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We do exactly the same work for the `tp-any` type, perfectly symmetric of the `tp-void` work."
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-ANY"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFVAR tp-any (MAKE-INSTANCE 'DTP :name 'tp-any))"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(TP-ANY)"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(SETF (sub tp-any) '(tp-void tp-any)\n",
" (sup tp-any) '(tp-any))"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(SET-FUNCALLABLE-INSTANCE-FUNCTION tp-any\n",
" #'(lambda (obj)\n",
" (declare (type t obj) (ignore obj))\n",
" (the boolean t)))"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-any 'anything)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The general process of initialization of a `DTP`-instance can now be defined. Firstly we need an `add-relation` function, allowing us to *add* a new order relation to the environment, something like \"`integer` < `number`\", *and all the consequent relations*. This function *updates* the `sub` and `sup` slots of the involved `DTP`-instances with the `union` Lisp function."
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFGENERIC ADD-RELATION (dtp1 dtp2))"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD ADD-RELATION ((dtp1 symbol) (dtp2 symbol))\n",
" (the list\n",
" (with-slots (sub) (eval dtp1)\n",
" (declare (type list sub))\n",
" (with-slots (sup) (eval dtp2)\n",
" (declare (type list sup))\n",
" (dolist (item sub)\n",
" (declare (type symbol item))\n",
" (setf (sup (eval item))\n",
" (union (sup (eval item)) sup)))\n",
" (dolist (item sup)\n",
" (declare (type symbol item))\n",
" (setf (sub (eval item))\n",
" (union (sub (eval item)) sub)))\n",
" (list dtp1 dtp2)))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A new `:after` method can then be defined this time for the `DTP`-type. Again because this is an `:after` method, the defined process is *added* to the standard initialization process, in particular giving the allocation of the instance, the initialization of arguments through initargs and initforms."
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD INITIALIZE-INSTANCE :after ((dtp dtp)\n",
" &key prdc\n",
" (dsub '(tp-void))\n",
" (dsup '(tp-any)))\n",
" (declare\n",
" (type (function (t) boolean) prdc)\n",
" (type list dsub dsup))\n",
" (with-slots (name sub sup) dtp\n",
" (declare\n",
" (type symbol name)\n",
" (type list sub sup))\n",
" (setf\n",
" sub (union dsub (list name))\n",
" sup (union dsup (list name)))\n",
" (dolist (item dsub)\n",
" (declare (type symbol item))\n",
" (add-relation item name))\n",
" (dolist (item dsup)\n",
" (declare (type symbol item))\n",
" (add-relation name item)))\n",
" (set-funcallable-instance-function dtp prdc))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This method for the generic function `initialize-instance` uses not only the `dtp`-instance to be initialized, but also the keyword optional arguments `:prdc` (predicate), `dsub` (direct subtypes) and `dsup` (direct supertypes). This mechanism works as follows: if an `initialize-instance` method uses a keyword argument, this argument is available to the user when it creates an instance of the corresponding class; usually this argument is used for a small work to be done during the initialization stage. Artificial example:"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defclass cc () ((slot :initarg :slot :reader slot)))"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defmethod initialize-instance :after ((cc cc) &key incslot)\n",
" (incf (slot-value cc 'slot) (* 2 incslot)))"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"CC-INSTANCE"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar cc-instance (make-instance 'cc :slot 3 :incslot 5))"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"13"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(slot cc-instance)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `:slot` argument, because it is an `:initarg`, is used to ordinarily initialize the unique slot named `slot` of a\n",
"`c`-instance; but the `:after` `initialize-instance` method, then adds twice the other keyword argument `:incslot`.\n",
"\n",
"For the initialization of a `dtp`-instance, the `:prdc` argument is used to define the predicate function defining the type, which is associated to the the `dtp`-instance and used when the `dtp`-instance is *funcalled*; the `:dsub` and `:dsup` arguments allow the user to give the list of *direct* sub- and super-types; the method then computes, with the help of `add-relation`, all the sub- and super-types. If not used, these arguments default to an obvious value.\n",
"\n",
"Let us define the `TP`-version of the boolean type."
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-BOOLEAN"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFVAR TP-BOOLEAN\n",
" (MAKE-INSTANCE 'DTP\n",
" :name 'TP-BOOLEAN\n",
" :prdc #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (if (member obj '(nil t))\n",
" t nil)))))"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-boolean 't)"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-boolean 'true)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"We use now the standard number types `tp-number`, `tp-integer` and `tp-fixnum` to give examples of use of the `:dsub` and `:dsup` arguments."
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-NUMBER"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar tp-number (make-instance 'dtp :name 'tp-number :prdc #'numberp))"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-FIXNUM"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar tp-fixnum\n",
" (make-instance 'dtp :name 'tp-fixnum\n",
" :prdc #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (typep obj 'fixnum)))\n",
" :dsup '(tp-number)))"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"TP-INTEGER"
]
},
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar tp-integer\n",
" (make-instance 'dtp :name 'tp-integer :prdc #'integerp\n",
" :dsub '(tp-fixnum) :dsup '(tp-number)))"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(TP-INTEGER TP-FIXNUM TP-NUMBER TP-ANY)"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(sup tp-fixnum)"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-fixnum 3)"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-fixnum 3.3)"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall tp-number 3.3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In particular the relations \"`tp-fixnum` < `integer`\" and \"`tp-integer` < `tp-number`\" have implied \"`tp-fixnum` < `tp-number`\". Note in these examples, the `prdc` slots for `tp-number` and `tp-integer` have been defined through symbols, for example `#'numberp` points to the functional value of the symbol `numberp`, in this case a predefined Lisp function examining whether its argument is a number. On the contrary, the `prdc` slot of the `tp-fixnum` `TP`-instance is a function constructed in the call of `make-instance`.\n",
"\n",
"The user may also construct several new types, and *after* the fact, explicitly using the `add-relation`, define the order relations between them."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T1"
]
},
"execution_count": 63,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar t1\n",
" (make-instance 'dtp\n",
" :name 't1\n",
" :prdc #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean (eql obj 1)))))"
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T12"
]
},
"execution_count": 64,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar t12\n",
" (make-instance 'dtp\n",
" :name 't12\n",
" :prdc #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (if (member obj '(1 2))\n",
" t nil)))))"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T123"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar t123\n",
" (make-instance 'dtp\n",
" :name 't123\n",
" :prdc #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (if (member obj '(1 2 3))\n",
" t nil)))))"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T1234"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar t1234\n",
" (make-instance 'dtp\n",
" :name 't1234\n",
" :prdc #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (if (member obj '(1 2 3 4))\n",
" t nil)))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We have constructed the types without using the `:dsub` and `:dsup` arguments. Then the user can maintain the structure of his type set; the `mapcar` shows the list of the `sup` slots of the just defined types."
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"((T1 TP-ANY) (T12 TP-ANY) (T123 TP-ANY) (T1234 TP-ANY))"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(mapcar #'sup (list t1 t12 t123 t1234))"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(T1 T12)"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(add-relation 't1 't12)"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"((T1 T12 TP-ANY) (T12 TP-ANY) (T123 TP-ANY) (T1234 TP-ANY))"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(mapcar #'sup (list t1 t12 t123 t1234))"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(T123 T1234)"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(add-relation 't123 't1234)"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"((T1 T12 TP-ANY) (T12 TP-ANY) (T123 T1234 TP-ANY) (T1234 TP-ANY))"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(mapcar #'sup (list t1 t12 t123 t1234))"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(T12 T123)"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(add-relation 't12 't123)"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"((T12 T1 T123 T1234 TP-ANY) (T1234 T123 T12 TP-ANY) (T123 T1234 TP-ANY) (T1234 TP-ANY))"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(mapcar #'sup (list t1 t12 t123 t1234))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What is striking in such a process is the fact that the order relations must be explained to the machine by the user, again a case of a necessary *intuitionistic* work! Of course it would be easy to design a simple generator of enumerative types which would itself determine the order relations that are satisfied, but, because of Cantor, Russell and Gödel, this is definitively impossible for general types.\n",
"\n",
"### The `FTP` and `SF` classes.\n",
"\n",
"Now we process the `FTP`-class, devoted to the `functional` types. To simplify the presentation, we consider only the functions $\\mathcal{T}_1\\rightarrow\\mathcal{T}_2$, in other words the functions with exactly *one* argument. It is easy to prove this is theoretically sufficient, but practically it is not. The standard trick consists in considering a function with two arguments as a function defined for a list of two elements. The further technicalities for the case of an arbitrary number of arguments are far from the subject of this paper, so that we do not want to consider them[8](#footnote_8).\n",
"\n",
" Common Lisp is also there fantastically more advanced than the other current languages: the numerous types of argument transfer that are available give the user a great flexibility to manage the various cases; they are allowed only because of the great *mathematical* precision of the definition of the language structure.\n",
"\n",
"The `FTP`-class is defined as follows:"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 74,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS FTP (TP)\n",
" ((sorc :type symbol :initarg :sorc :reader sorc)\n",
" (trgt :type symbol :initarg :trgt :reader trgt))\n",
" (:metaclass funcallable-standard-class))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This time, two slots, `sorc` (source) and `trgt` (target), are added to the `name` slot of the underlying `TP`-structure; these slots must be two `TP`-types, in fact two symbols locating them, according to our organization. Note these source and target types can be discriminant or functional.\n",
"\n",
"A functional object will be an instance of the class `SF`, for s*afe*-f*unction*, safe because the types of argument and result are systematically verified when the function is called:"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS SF ()\n",
" ((sorc :type symbol :initarg :sorc :reader sorc)\n",
" (trgt :type symbol :initarg :trgt :reader trgt))\n",
" (:metaclass funcallable-standard-class))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The definition of `FTP` and `SF` are almost the same! In the case of an `FTP`-instance, the associated function will examine whether some object is in this type. In the case of an `SF`-instance, the associated function will be the functional object itself; the slots `sorc` and `trgt` are then additional information about the correct types of argument and result.\n",
"\n",
"To explain the main part of the work to be done now, let us consider a functional type $\\mathcal{T}_1\\rightarrow\\mathcal{T}_2$ and a particular function $f$ declared to be $f:\\mathcal{T}'_1\\rightarrow\\mathcal{T}'_2$; under what circumstances, is this particular $f$ of the functional type $\\mathcal{T}_1\\rightarrow\\mathcal{T}_2$? There is only one solution: we must have the order relations: $\\mathcal{T}_1\\leq\\mathcal{T}'_1$ and $\\mathcal{T}_2\\leq\\mathcal{T}'_2$, and the reader understands now why we took care of these order relations. We therefore need a `subtypep` Lisp function to compare types. If both arguments are discriminant types, the answer is read in a `sup` slot; if both arguments are functional, the appropriate comparisons must be applied to the respective sources and targets; if the types are not of the same nature, one being discriminant and the other one being functional, the answer is certainly negative[9](#footnote_9).\n",
"\n",
" It would be easy to work only with (pseudo-) \"discriminant\" types, even for functional objects, but the organization chosen here makes more obvious the deep difference of nature between both kinds of types; in particular in this way, a `DTP`-type and an `FTP`-type can never be compared.\n",
"\n",
"The Lisp translation:"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"SUBTYPE-P"
]
},
"execution_count": 76,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFUN SUBTYPE-P (tp1 tp2)\n",
" (declare (type symbol tp1 tp2))\n",
" (the boolean\n",
" (etypecase (eval tp1)\n",
" (dtp\n",
" (etypecase (eval tp2)\n",
" (dtp (if (member tp2 (sup (eval tp1)))\n",
" t nil))\n",
" (ftp nil)))\n",
" (ftp\n",
" (etypecase (eval tp2)\n",
" (dtp nil)\n",
" (ftp (and (subtype-p (sorc (eval tp2)) (sorc (eval tp1)))\n",
" (subtype-p (trgt (eval tp1)) (trgt (eval tp2))))))))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We cannot try the `subtype-p` function until the initialization work for `FTP`-instances has been completed. Remember in general a `TP`-instance must also be *funcallable* to verify the type of any object. The standard initialization work is therefore completed as follows."
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD INITIALIZE-INSTANCE :after ((ftp ftp) &rest rest)\n",
" (declare (ignore rest))\n",
" (set-funcallable-instance-function ftp\n",
" #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (and (typep obj 'sf)\n",
" (subtype-p (sorc ftp) (sorc obj))\n",
" (subtype-p (trgt obj) (trgt ftp)))))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This time the associated function is entirely deduced from the `sorc` and `trgt` slots, available *after* the standard initialization work. You see the *only* objects of type some `FTP`-descriptor are `SF`-instances, and the appropriate order relations must be verified between source, target and the considered functional type. Now we can create a\n",
"few `FTP`-instances and do the obvious tests for `subtype-p`."
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(subtype-p 'tp-fixnum 'tp-integer)"
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(subtype-p 'tp-fixnum 'tp-fixnum)"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(subtype-p 'tp-integer 'tp-fixnum)"
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"FII"
]
},
"execution_count": 81,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar fii (make-instance 'ftp\n",
" :name 'fii\n",
" :sorc 'tp-integer\n",
" :trgt 'tp-integer))"
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"FNF"
]
},
"execution_count": 82,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar fnf (make-instance 'ftp\n",
" :name 'fnf\n",
" :sorc 'tp-number\n",
" :trgt 'tp-fixnum))"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(subtype-p 'fii 'tp-fixnum)"
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 84,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(subtype-p 'fii 'fnf)"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 85,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(subtype-p 'fnf 'fii)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In order to make them work, the initialization of the `SF`-instances remains to be defined. An `SF`-instance is funcallable and the associate function will be the ordinary function the user intends to define, with the type verifications automatically added. The Lisp translation:"
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD INITIALIZE-INSTANCE :after ((sf sf) &key f)\n",
" (declare (type (function (t) t) f))\n",
" (set-funcallable-instance-function\n",
" sf\n",
" (with-slots (sorc trgt) sf\n",
" #'(lambda (arg)\n",
" (declare (type t arg))\n",
" (unless (funcall (eval sorc) arg)\n",
" (error \"The argument ~S should be of type ~S.\"\n",
" arg sorc))\n",
" (the t\n",
" (let ((rslt (funcall f arg)))\n",
" (unless (funcall (eval trgt) rslt)\n",
" (error \"The result ~S should be of type ~S.\"\n",
" rslt trgt))\n",
" rslt))))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the extra `:f`initialization argument. An obvious test:"
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2+"
]
},
"execution_count": 87,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar 2+ (make-instance 'sf\n",
" :sorc 'tp-integer\n",
" :trgt 'tp-integer\n",
" :f #'(lambda (n) (+ n 2))))"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"7"
]
},
"execution_count": 88,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 2+ 5)"
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The argument 5.5 should be of type TP-INTEGER.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 89,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 2+ 5.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Improvements.\n",
"\n",
"It is a little painful to use the `make-instance` method with its keywords. Let's use another aspect of the Common Lisp environment, the *macro generator* to automate the appropriate call to `make-instance`. Here we detail the work for the creation of `SF`-instances."
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"MAKE-SF"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMACRO MAKE-SF (sorc trgt f)\n",
" `(make-instance 'sf\n",
" :sorc ,sorc :trgt ,trgt :f ,f))"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(MAKE-INSTANCE (QUOTE SF) :SORC (QUOTE NUMBER) :TRGT (QUOTE TP-INTEGER) :F (FUNCTION (LAMBDA (N) (+ N 5))))"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(macroexpand\n",
" '(make-sf 'number 'tp-integer #'(lambda (n) (+ n 5)))))"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"5+"
]
},
"execution_count": 92,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar 5+ (make-sf 'tp-number 'tp-integer\n",
" #'(lambda (n) (+ n 5))))"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"11"
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 5+ 6)"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The result 11.5 should be of type TP-INTEGER.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 94,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 5+ 6.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `defmacro` must be considered as defining a *source text translator* converting a call to `make-sf` into some\n",
"`make-instance`. The translation mechanism is *tested* by using the `macroexpand` Lisp function. Then the `5+` function is really created and then used. This time the error is in the result type.\n",
"\n",
"Constructing the `DTP` and `FTP` instances can be processed in the same way."
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"MAKE-DTP"
]
},
"execution_count": 95,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMACRO MAKE-DTP (name prdc)\n",
" `(make-instance 'dtp\n",
" :name ,name :prdc ,prdc))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To explain the coherence of this type system, we add another stage of verification: a discriminant type is essentially a function $\\mathcal{A}\\rightarrow\\mathcal{B}$, so that the functional type of this function should be also verified. Let us solve this challenge. We slightly modify the `DTP`-initializer."
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"REDEFINITION-WITH-DEFMETHOD: \n",
" redefining INITIALIZE-INSTANCE :AFTER (#) in DEFMETHOD\n"
]
},
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 96,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD INITIALIZE-INSTANCE :after ((dtp dtp)\n",
" &key prdc\n",
" (dsub '(tp-void))\n",
" (dsup '(tp-any)))\n",
" (declare\n",
" (type (function (t) boolean) prdc)\n",
" (type list dsub dsup))\n",
" (with-slots (name sub sup) dtp\n",
" (declare\n",
" (type symbol name)\n",
" (type list sub sup))\n",
" (setf\n",
" sub (union dsub (list name))\n",
" sup (union dsup (list name)))\n",
" (dolist (item dsub)\n",
" (declare (type symbol item))\n",
" (add-relation item name))\n",
" (dolist (item dsup)\n",
" (declare (type symbol item))\n",
" (add-relation name item)))\n",
" (set-funcallable-instance-function dtp\n",
" (make-sf 'tp-any 'tp-boolean prdc)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As an example, let us consider the frequent case of a user who forgets the positive answer of the `member` function is not the boolean `T`:"
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(3 4 5)"
]
},
"execution_count": 97,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(member 3 '(1 2 3 4 5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Such a user could erroneously define an enumerative type as follows:"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"COLORS"
]
},
"execution_count": 98,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar colors (make-dtp 'colors\n",
" #'(lambda (obj)\n",
" (member obj '(red orange yellow green blue indigo violet)))))"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 99,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall colors 'black)"
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The result (BLUE INDIGO VIOLET) should be of type TP-BOOLEAN.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall colors 'blue)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and you see the type error ... during the type verification is detected and a clear message is displayed. This cannot be applied to the `boolean` type itself, because an infinite loop would be generated. You see also in such a case the functional object associated with an instance (`DTP`) is not purely functional but another (`SF`)-instance which in turn has an associated functional object.\n",
"\n",
"### The function `compose`.\n",
"\n",
"It is customary in discussions between Lisp and C++ or Java programmers to use the example of the `compose` function to illustrate how weak C++ is when *functional programming* is involved; have a look at [[CMPS]](#CMPS) for the \"official\" solution in C++ and see how technically difficult it is. Here we give a simple Lisp solution, which furthermore is at once valid for arbitrary data types.\n",
"\n",
"Firstly we construct the `pair` and `any-sf` types[10](#footnote_10):\n",
"\n",
" Exercise: a previous version of this paper had the claimed \"subtle\" definition of the `any-sf` type: `(make-ftp 'any-sf 'tp-void 'tp-any)`; this really defines a type, but the `compose` `SF`-instance would not have the `any-sf` type, why? Analyzing precisely the difference between both definitions for `any-sf` is quite interesting and shows that any *strict* typing system cannot be entirely satisfactory, some *freedom* must necessarily be given to the programmer; it is exactly the point of view of the Common Lisp designers."
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"PAIR"
]
},
"execution_count": 101,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar pair (make-dtp 'pair\n",
" #'(lambda (obj)\n",
" (and (consp obj)\n",
" (consp (cdr obj))\n",
" (null (cddr obj))))))"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"ANY-SF"
]
},
"execution_count": 102,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar any-sf (make-dtp 'any-sf\n",
" #'(lambda (obj)\n",
" (typep obj 'sf))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then the `2-sf-that-may-be-composed` type may be defined[11](#footnote_11).\n",
"\n",
" Exercise: Design a `make-subdtp` constructor to automate the generation of the `:dsup` argument which uses the predicate associated to the initial type."
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2-SF-THAT-MAY-BE-COMPOSED"
]
},
"execution_count": 103,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar 2-sf-that-may-be-composed \n",
" (make-dtp '2-sf-that-may-be-composed\n",
" #'(lambda (obj)\n",
" (and (funcall pair obj)\n",
" (funcall any-sf (first obj))\n",
" (funcall any-sf (second obj))\n",
" (subtype-p (trgt (second obj)) (sorc (first obj)))))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> GH: Fix the `make-dtp` macro to accomodate the `:dsup` parameter!"
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(PAIR)"
]
},
"execution_count": 104,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(setf (sup 2-sf-that-may-be-composed) '(pair))"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"COMPOSE"
]
},
"execution_count": 105,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar compose\n",
" (make-sf '2-sf-that-may-be-composed 'any-sf\n",
" #'(lambda (arg)\n",
" (let ((sf2 (first arg))\n",
" (sf1 (second arg)))\n",
" (make-sf (sorc sf1) (trgt sf2)\n",
" #'(lambda (arg)\n",
" (funcall sf2 (funcall sf1 arg))))))))"
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4+"
]
},
"execution_count": 106,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar 4+ (funcall compose (list 2+ 2+)))"
]
},
{
"cell_type": "code",
"execution_count": 107,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"9"
]
},
"execution_count": 107,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 4+ 5)"
]
},
{
"cell_type": "code",
"execution_count": 108,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The argument 5.5 should be of type TP-INTEGER.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 108,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 4+ 5.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A more subtle example: the composed function is in the right type with respect the argument, but a *used* function is not."
]
},
{
"cell_type": "code",
"execution_count": 109,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3/2+"
]
},
"execution_count": 109,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar 3/2+ (make-sf 'tp-integer 'tp-integer\n",
" #'(lambda (x) (+ x 3/2))))"
]
},
{
"cell_type": "code",
"execution_count": 110,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3+"
]
},
"execution_count": 110,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar 3+ (funcall compose (list 3/2+ 3/2+)))"
]
},
{
"cell_type": "code",
"execution_count": 111,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The result 15/2 should be of type TP-INTEGER.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 111,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall 3+ 6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The last type-error example; this time, the `compose` function itself observes its argument does not have the required type."
]
},
{
"cell_type": "code",
"execution_count": 112,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The argument (# #) should be of type 2-SF-THAT-MAY-BE-COMPOSED.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 112,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(funcall compose (list compose compose))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# CLOS and Mathematical Structures.\n",
"\n",
"We give a small example to explain how CLOS can easily be used to implement the classical *mathematical structures*. Most often, an object of some *type* in mathematics is a structure with several components, frequently of *functional* nature. In this small presentation, we consider the case of a user who wants to handle *sets*, *magmas*, *associative* magmas and *monoids*; these simple particular cases are sufficient to understand how CLOS gives the right tools to process mathematical structures. In the following section, we will describe how these simple methods have been used in [[DRSS]](#DRSS) to implement the main structures of *Algebraic Topology* such as *chain complexes*, *simplicial sets*,\n",
"*simplicial groups*, *Hopf algebras*, and also the various *morphisms* between these objects.\n",
"\n",
"## Sets.\n",
"\n",
"We define a `/SET/` class whose instances correspond to the *sets* of the classical *set theory*."
]
},
{
"cell_type": "code",
"execution_count": 113,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 113,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS /SET/ ()\n",
" ((name :type symbol :initarg :name :initform (gensym) :reader name)\n",
" (prdcf :type function :initarg :prdcf :reader prdcf)\n",
" (cmprf :type function :initarg :cmprf :reader cmprf)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this organization, a set is made of three slots. The `name` slot has the same role as for the types of the previous section: it is only an auxiliary tool to locate easily the sets; the slot contains the symbol that locates the set, and we automate this organization:"
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 114,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD SHARED-INITIALIZE :after ((set /set/) slot-names &rest rest)\n",
" (declare (ignore slot-names rest))\n",
" (set (name set) set))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The appropriate `print-object` method; when a *set* or an object of a subclass will be displayed, the output will show the corresponding class and the `name`:"
]
},
{
"cell_type": "code",
"execution_count": 115,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 115,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRINT-OBJECT ((set /set/) stream)\n",
" (declare (type stream stream))\n",
" (format stream \"#<~S ~S>\" (class-name (class-of set)) (name set))\n",
" (the /set/ set))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The second slot of a set, namely the `prdcf` slot (predicate-function), contains a function which can be called to examine whether some *arbitrary* object is an element of the set. In order to use this slot easily, we define two functions, `owns` and `in`; the first one may answer yes or no, that is, `t` or `nil`; and the second one generates an *error* if the membership relation is not satisfied[12](#footnote_12).\n",
"\n",
" The sections bout our two main didactical examples, typing and mathematical categories, are entirely *independent*; but the lucid reader will observe the root class of the second application, namely the `/set/` class, can after all be also considered as a typing system in a mathematical context!"
]
},
{
"cell_type": "code",
"execution_count": 116,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"OWNS"
]
},
"execution_count": 116,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFUN OWNS (set obj)\n",
" (declare\n",
" (type /set/ set)\n",
" (type t obj))\n",
" (the boolean\n",
" (funcall (prdcf set) obj)))"
]
},
{
"cell_type": "code",
"execution_count": 117,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"IN"
]
},
"execution_count": 117,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFUN IN (set obj)\n",
" (declare\n",
" (type /set/ set)\n",
" (type t obj))\n",
" (if (funcall (prdcf set) obj)\n",
" obj\n",
" (error \"The object ~S is not in ~S.\"\n",
" obj set)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The explanation of the third slot `cmpr` is given a little later; we construct the set `N` of non-negative integers."
]
},
{
"cell_type": "code",
"execution_count": 118,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"N"
]
},
"execution_count": 118,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar N\n",
" (MAKE-INSTANCE '/set/\n",
" :name 'N\n",
" :prdcf #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (and (integerp obj)\n",
" (>= obj 0))))\n",
" :cmprf #'=))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The symbol `N` locates the constructed set"
]
},
{
"cell_type": "code",
"execution_count": 119,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 119,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"N"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and the function `owns` allows the user to examine the membership relation:"
]
},
{
"cell_type": "code",
"execution_count": 120,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 120,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(owns N +1)"
]
},
{
"cell_type": "code",
"execution_count": 121,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 121,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(owns N -1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `cmprf` slot is essential in this context. There are frequently strong differences between an element of a set *as thought by the mathematician* and its possible machine representations, the *s* being important. The notion of *equality* in mathematics is \"primitive\" and rarely *logically* considered by mathematicians. On the contrary, this notion is crucial in Computer Science, and Common Lisp is by far the most precise language from this point of view[13](#footnote_13). Here, we want to let the user freely decide how equality is defined between the elements of his sets, and this is the role of the `cmprf` slot (comparison function). For example, the comparison between elements in the set `N` is done by the Lisp predefined function `#'=`, in particular appropriate to compare integers. Again we define a function `cmpr` to easily use the `cmprf` slot of a set.\n",
"\n",
" In particular all the standard predefined Lisp functions allow the user to *freely* define the equality relation to be used for every particular call."
]
},
{
"cell_type": "code",
"execution_count": 122,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"ELT-CMPR"
]
},
"execution_count": 122,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFUN ELT-CMPR (set elmn1 elmn2)\n",
" (declare\n",
" (type /set/ set)\n",
" (type t elmn1 elmn2))\n",
" (the boolean\n",
" (funcall (cmprf set)\n",
" (in set elmn1) (in set elmn2))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the use of the `in` function to verify that the compared elements are really in the considered set. Now we can compare two elements of `N`."
]
},
{
"cell_type": "code",
"execution_count": 123,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 123,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr N 4 9)"
]
},
{
"cell_type": "code",
"execution_count": 124,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The object -9 is not in #.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 124,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr N 4 -9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us construct now the set `Z/5`, that is the set of *integers modulo 5*. In our context, the most elegant method to implement this set consists in admitting a representation of an element of `Z/5` by an *arbitrary* integer, possibly negative or `> 4`, and to define the comparison with the help of the `mod` function, a predefined Lisp function."
]
},
{
"cell_type": "code",
"execution_count": 125,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Z/5"
]
},
"execution_count": 125,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar Z/5\n",
" (MAKE-INSTANCE '/SET/\n",
" :name 'Z/5\n",
" :prdcf #'integerp\n",
" :cmprf #'(lambda (elmn1 elmn2)\n",
" (= 0 (mod (- elmn1 elmn2) 5)))))"
]
},
{
"cell_type": "code",
"execution_count": 126,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 126,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr Z/5 4 9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This time the comparison between 4 and 9 is positive: these machine integers are *different* representations of the *same* mathematical object. You will understand that it is easy in this framework to define *quotient* sets.\n",
"\n",
"### Magmas.\n",
"\n",
"A magma is a set provided with a law of composition without any particular required property. It is a set with an additional ingredient, a function able to work on two elements of the magma and returning another element, their composition according the composition law. It is natural in this context to define the `magma` *subclass* of the `/set/` class; any `magma` instance is a set with a further slot, the `lawf` slot."
]
},
{
"cell_type": "code",
"execution_count": 127,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 127,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS MAGMA (/set/)\n",
" ((lawf :type function :initarg :lawf :reader lawf)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Again a function is added to the environment allowing the user to easily refer the `lawf` slot of a magma."
]
},
{
"cell_type": "code",
"execution_count": 128,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 128,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFGENERIC LAW (magma &rest rest))"
]
},
{
"cell_type": "code",
"execution_count": 129,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 129,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD LAW ((magma magma) &rest rest)\n",
" (case (length rest)\n",
" (1 (in magma (first rest)))\n",
" (2 (in magma\n",
" (funcall (lawf magma)\n",
" (in magma (first rest))\n",
" (in magma (second rest)))))\n",
" (otherwise\n",
" (error \"Non-correct arguments in:~@\n",
" (LAW ~S~{ ~S~}).\" magma rest))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We intend in general to allow the user to refer the *law* defining a magma with an *arbitrary* number of elements, at least if this makes sense. In such a case, CLOS allows the user to define a generic function, here the `law` generic function, with one mandatory argument, called here `magma` and any number of other arguments put together in a list reachable through the symbol `rest`[14](#footnote_14). The ingredient `(magma magma)` in the parameter list has two meanings: the first `magma` names the first parameter, and the second `magma` explains the corresponding argument must be of *class* `magma`, otherwise the method is not applicable.\n",
"\n",
" More precisely, the symbol `&rest` *marks* in the parameter list a new zone, in this case with a symbol locating the \"other\" arguments; the Lisp programmer almost always chooses the symbol `rest` to locate this list.\n",
"\n",
"For a magma without any further claimed property, it is sensible to allow one argument, and then this argument is returned, or two arguments and then the *product* of these arguments according to the composition law is returned.\n",
"Otherwise an error message is displayed. Note how the `in` function is used to verify the correct type of the arguments.\n",
"\n",
"We have defined the *set* `N` and it is possible to transform it now into a *magma*. Note how the standard initialization process of an instance allows the user to obtain such a conversion without any further work."
]
},
{
"cell_type": "code",
"execution_count": 130,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 130,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class N 'magma\n",
" :lawf #'(lambda (n1 n2)\n",
" (* n1 (- n2 3))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The *law* which is defined is $(n_1,n_2) \\mapsto n_1(n_2-3)$. The obvious trials:"
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"8"
]
},
"execution_count": 131,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law N 4 5)"
]
},
{
"cell_type": "code",
"execution_count": 132,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The object -5 is not in #.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 132,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law N 4 -5)"
]
},
{
"cell_type": "code",
"execution_count": 133,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The object -3 is not in #.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 133,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law N 1 0)"
]
},
{
"cell_type": "code",
"execution_count": 134,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 134,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law N 4)"
]
},
{
"cell_type": "code",
"execution_count": 135,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"Non-correct arguments in:\n",
"(LAW # 4 5 6).\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 135,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law N 4 5 6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The constructed magma in fact is not correct; let us construct a classical correct one:"
]
},
{
"cell_type": "code",
"execution_count": 136,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Q"
]
},
"execution_count": 136,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(defvar Q \n",
" (make-instance 'magma\n",
" :name 'Q\n",
" :prdcf #'rationalp\n",
" :cmprf #'=\n",
" :lawf #'+))"
]
},
{
"cell_type": "code",
"execution_count": 137,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"13/6"
]
},
"execution_count": 137,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law Q 3/2 2/3)"
]
},
{
"cell_type": "code",
"execution_count": 138,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"Non-correct arguments in:\n",
"(LAW # 3/2 2/3 4/5).\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 138,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law Q 3/2 2/3 4/5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is simply the set `Q` of rationals equipped with the standard addition law. The last result is not satisfactory: the law is *associative* and it should be possible to make operate this law on an arbitrary number of arguments. The solution consists in defining a new class `A-MAGMA` (associative magma). There is no new slot in this class: membership of the new class only means the object, some magma, has an associative law."
]
},
{
"cell_type": "code",
"execution_count": 139,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 139,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS A-MAGMA (magma) ())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But this new class may be used to define a new method for the generic function `law`; in this new case, an arbitrary *positive* number of arguments can be used. A simple recursive process defines the new method from the old one: if the argument number is three or more, the computation is decomposed, otherwise the *next* method is called; the law is associative and this definition is coherent."
]
},
{
"cell_type": "code",
"execution_count": 140,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 140,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD LAW ((a-magma a-magma) &rest rest)\n",
" (if (> (length rest) 2)\n",
" (law a-magma\n",
" (in a-magma (first rest))\n",
" (apply #'law a-magma (rest rest)))\n",
" (call-next-method)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You see in particular how the very basic Lisp function `apply` allows to recall the same method with one argument removed. We inform now the environment that our magma `Q` is associative:"
]
},
{
"cell_type": "code",
"execution_count": 141,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 141,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class Q 'a-magma)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"which allows us to compute the composition in `Q` of an arbitrary number of elements. Because the `magma` method will eventually be called, a possible type fault is intercepted."
]
},
{
"cell_type": "code",
"execution_count": 142,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"163/60"
]
},
"execution_count": 142,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law Q 1/2 2/3 3/4 4/5)"
]
},
{
"cell_type": "code",
"execution_count": 143,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The object 0.75 is not in #.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 143,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law Q 1/2 2/3 0.75 4/5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Monoids.\n",
"\n",
"And the process can be continued for ever and ever, allowing the user to enrich as far as necessary the considered mathematical structures. Here, the following step consists in considering the structure of *monoids*. A monoid is an\n",
"associative magma with a unit. Only a `unit` slot is to be added to the `a-magma` structure. It is impossible in general to verify the claimed *unit* $e$ satisfies the required property, but however the property $e\\ast e = e$ can be tested; doing this test through a `shared-initialize` method allows it to be used in a `make-instance`, or in a `change-class`, or in a `reinitialize-instance` as well. The membership of the monoid may be also tested."
]
},
{
"cell_type": "code",
"execution_count": 144,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 144,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFCLASS MONOID (a-magma)\n",
" ((unit :initarg :unit :reader unit)))"
]
},
{
"cell_type": "code",
"execution_count": 145,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 145,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD SHARED-INITIALIZE :after ((monoid monoid) slot-names &rest rest)\n",
" (declare (ignore rest))\n",
" (let ((unit (unit monoid)))\n",
" (unless (owns monoid unit)\n",
" (error \"Sorry, the claimed unit ~S is not in ~S.\"\n",
" unit monoid))\n",
" (unless (elt-cmpr monoid unit (law monoid unit unit))\n",
" (error \"Sorry, ~S does not look like a unit in ~S.\"\n",
" unit monoid))))"
]
},
{
"cell_type": "code",
"execution_count": 146,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"Sorry, 1 does not look like a unit in #.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 146,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class Q 'monoid :unit 1)"
]
},
{
"cell_type": "code",
"execution_count": 147,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 147,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(reinitialize-instance Q :unit 0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the error about the unit is intercepted *after* the standard initialization process, so that our `Q` is then become a `monoid`, as observed in the error message, but a wrong monoid; it is natural in this situation to use `reinitialize-instance` to redefine the object. A little more sophisticated use of `:around` methods would allow us to verify the coherence `before` changing class, and to refuse it if detected non-coherent.\n",
"\n",
"It is interesting to convert `Z/5`, currently a set, into a monoid with the unit `10`. Why not?"
]
},
{
"cell_type": "code",
"execution_count": 148,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 148,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(change-class Z/5 'monoid\n",
" :lawf #'+\n",
" :unit 10)"
]
},
{
"cell_type": "code",
"execution_count": 149,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 149,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr Z/5 10 (law Z/5 10 10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In a monoid, the composition law may logically be called for *zero* argument; in this case, the *unit* is the result. A new method for the generic function `law` is therefore stacked."
]
},
{
"cell_type": "code",
"execution_count": 150,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 150,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD LAW ((monoid monoid) &rest rest)\n",
" (if (= 0 (length rest))\n",
" (unit monoid)\n",
" (call-next-method)))"
]
},
{
"cell_type": "code",
"execution_count": 151,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(0 10)"
]
},
"execution_count": 151,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(list (law Q) (law Z/5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This looks a little artificial because `(law Q)` and `(unit Q)` are in fact synonymous, but think of the frequent case where you have to process a statement like `(apply #'law Q some-list)` where the last argument is a *computed* list, which could be sometimes empty; with our new `monoid` method for the `law` generic function, even if the value of `some-list` is the empty list, the correct process is applied.\n",
"\n",
"### What about the morphisms?\n",
"\n",
"So far we have only worked with the *objects* of the mathematical categories of sets, magmas, associative magmas, monoids. What about the *morphisms*? The solution is easy, but needs a little lucidity about the usual *compatibility* properties with ambient structures. We only sketch a possible solution, the expansion of which being obvious.\n",
"\n",
"The root of our classes of morphisms is the class of morphisms between sets.\n",
"\n",
"```lisp\n",
"(DEFCLASS SET-MRPH ()\n",
" ((sorc :type set :initarg :sorc :reader sorc)\n",
" (trgt :type set :initarg :trgt :reader trgt)\n",
" (f :type function :initarg :f :reader f)))\n",
"```\n",
"\n",
"A `set-mrph` instance is essentially a *source*, some set, a *target*, some set, and a function mapping any object of the source to an object of the target. Again the standard *functional* properties of Common Lisp give the developper the right environment. We could also hide the `f` slot into the special hidden functional slot of a *funcallable* instance, as we did for the `TP`-instances in [Section 3](#What-typing-is.); it is only a question of taste.\n",
"\n",
"We need something to be able to \"funcall\" a `set-mrph`:\n",
"\n",
"```lisp\n",
"(DEFUN ? (mrph elmn)\n",
" (...\n",
" code which:\n",
" 1) verifies elmn is in the source of mrph;\n",
" 2) applies the f slot of mrph to elmn to compute the image;\n",
" 3) verifies this image is in the target of mrph;\n",
" 4) returns the image.\n",
" ))\n",
"\n",
"(? some-set-mrph some-source-element)\n",
"```\n",
"\n",
"Now a magma-morphism is nothing but a set-morphism satisfying the standard compatibility properties with the magma structures of source and target. So that:\n",
"\n",
"```lisp\n",
"(DEFCLASS MAGMA-MRPH (set-mrph) ())\n",
"```\n",
"\n",
"and the same for `a-magma-mrph`, `monoid-mrph` and so on. Note this organization allows the user to clearly distinguish a\n",
"*set*-morphism between magmas from a *magma*-morphism between the same magmas; in the first case the compatibility properties are not satisfied or optional; in the second case the compatibility properties are assumed satisfied. Such morphisms are intensively used in the Kenzo program and considered in the following section.\n",
"\n",
"### What about functors?\n",
"\n",
"Working with categories, the mathematician inevitably will have to work with functors. In this organisation a tower of compatible functors is nothing but *one* generic function with various methods corresponding to the different cases. For example the *cartesian product* functor is defined for sets, magmas, associative magmas, monoids, and many other categories. It is sufficient to implement *all these functors* in *one* generic function, as follows."
]
},
{
"cell_type": "code",
"execution_count": 152,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 152,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFGENERIC PRODUCT (set1 set2))"
]
},
{
"cell_type": "code",
"execution_count": 153,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 153,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRODUCT ((set1 /set/) (set2 /set/))\n",
" (make-instance '/set/\n",
" :name (intern (format nil \"~S-PRDC-~S\" (name set1) (name set2)))\n",
" :prdcf #'(lambda (obj)\n",
" (declare (type t obj))\n",
" (the boolean\n",
" (and (listp obj)\n",
" (= 2 (length obj))\n",
" (owns set1 (first obj))\n",
" (owns set2 (second obj)))))\n",
" :cmprf #'(lambda (pair1 pair2)\n",
" (declare (type list pair1 pair2))\n",
" (the boolean\n",
" (and (elt-cmpr set1 (first pair1) (first pair2))\n",
" (elt-cmpr set2 (second pair1) (second pair2)))))))"
]
},
{
"cell_type": "code",
"execution_count": 154,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 154,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(product N Z/5)"
]
},
{
"cell_type": "code",
"execution_count": 155,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 155,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr N-prdc-Z/5 '(4 5) '(9 5))"
]
},
{
"cell_type": "code",
"execution_count": 156,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"T"
]
},
"execution_count": 156,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr N-prdc-Z/5 '(4 5) '(4 10))"
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"SIMPLE-ERROR: \n",
"The object (-4 10) is not in #.\n"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 157,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(elt-cmpr N-prdc-Z/5 '(4 5) '(-4 10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You see how clear and natural is the code of the `product` method. It is only a question of constructing the appropriate `prdcf` and `cmprf` slots, some functions, from the corresponding slots of the arguments `set1` and `set2`; the process is always the same, so that the Lisp *closures*, a subtle notion not available in C++ or Java[15](#footnote_15), give the user the natural tool, even he does not know exactly what a closure is!\n",
"\n",
" available in Maple since the Release 5, but no OOP in Maple...\n",
"\n",
"For the richer structures of `magma`, `a-magma` and `monoid`, it is sufficient to write down new specific methods for the *same* generic function, defining the additional work to be done, using the previous work thanks to `call-next-method`."
]
},
{
"cell_type": "code",
"execution_count": 158,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 158,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRODUCT ((magma1 magma) (magma2 magma))\n",
" (change-class (call-next-method) 'magma\n",
" :lawf #'(lambda (pair1 pair2)\n",
" (list\n",
" (law magma1 (first pair1) (first pair2))\n",
" (law magma2 (second pair1) (second pair2))))))"
]
},
{
"cell_type": "code",
"execution_count": 159,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 159,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRODUCT ((magma1 a-magma) (magma2 a-magma))\n",
" (change-class (call-next-method) 'a-magma))"
]
},
{
"cell_type": "code",
"execution_count": 160,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 160,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFMETHOD PRODUCT ((monoid1 monoid) (monoid2 monoid))\n",
" (change-class (call-next-method) 'monoid\n",
" :unit (list (unit monoid1) (unit monoid2))))"
]
},
{
"cell_type": "code",
"execution_count": 161,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Q2"
]
},
"execution_count": 161,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFVAR Q2 (product Q Q))"
]
},
{
"cell_type": "code",
"execution_count": 162,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Q4"
]
},
"execution_count": 162,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(DEFVAR Q4 (product Q2 Q2))"
]
},
{
"cell_type": "code",
"execution_count": 163,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"((4/3 32/21) (13/8 76/45))"
]
},
"execution_count": 163,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(law Q4 '((1/2 2/3) (3/4 4/5))\n",
" '((5/6 6/7) (7/8 8/9)))"
]
},
{
"cell_type": "code",
"execution_count": 164,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"((0 0) (0 0))"
]
},
"execution_count": 164,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(unit Q4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note also the applicability rule for methods of a generic function implies the product of a monoid by a magma, for example, will fortunately be a magma."
]
},
{
"cell_type": "code",
"execution_count": 165,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"#"
]
},
"execution_count": 165,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(product Q N)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# CLOS and the Kenzo program.\n",
"\n",
"> Some of the comments in this section apply to an [older Kenzo version](https://github.com/gheber/kenzo/tree/master/archive/Kenzo-2). They are called out whenever there's a difference in implementation and behavior with the [newer version](https://github.com/gheber/kenzo).\n",
"\n",
"The Kenzo program is the first significant *machine program* about classical Algebraic Topology. It is not only a program implementing various *known* algorithms; *new* methods have been developped to *transform* the main \"tools\" of Algebraic Topology, mainly the spectral sequences, not at all *algorithmic* in the traditionnal organisation, into actual *computing* methods.\n",
"\n",
"## An example of Kenzo work.\n",
"\n",
"Let us show a simple example to illustrate which is possible with this program. The homology group $H_5 \\Omega^3 \\mathrm{Moore}(\\mathbb{Z}_2,4)$[16](#footnote_16) is \"in principle\" reachable thanks to old methods, see [[CRML]](#CRML), but experience\n",
"shows even the most skilful topologists meet some difficulties to determine it, see [[RBSR6]](#RBSR6)[[SRGR5]](#SRGR5). With the Kenzo program, you construct the Moore space.\n",
"\n",
" The space $\\mathrm{Moore}(\\mathbb{Z}_2,4)$ is a \"canonical\" space having only non-trivial homology in dimension~4, namely $\\mathbb{Z}_2$, and $\\Omega^3\\mathrm{Moore}(\\mathbb{Z}_2,4)$, its third loop space, is the space of continuous maps from the 3-sphere~$S^3$ to this Moore space; the challenge is to determine the fifth homology group of this functional space."
]
},
{
"cell_type": "code",
"execution_count": 166,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"---done---"
]
},
{
"data": {
"text/plain": [
"NIL"
]
},
"execution_count": 166,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(cat-init)"
]
},
{
"cell_type": "code",
"execution_count": 167,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[K1 Simplicial-Set]"
]
},
"execution_count": 167,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(def m4 (moore 2 4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The program returns the Kenzo-object `#1`, a simplicial set, that is, a combinatorial version of the Moore space which is asked for, and this object is assigned to the symbol `m4`. Then you construct the third loop-space of this Moore space."
]
},
{
"cell_type": "code",
"execution_count": 168,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[K30 Simplicial-Group]"
]
},
"execution_count": 168,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(def o3m4 (loop-space m4 3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The combinatorial version of the loop space is *highly* infinite: it is a combinatorial version of the space of *continuous* maps $S^3 \\rightarrow \\mathrm{Moore}(\\mathbb{Z}_2,4)$ but functionnally coded as a small set of functions\n",
"in a `simplicial-group` object, that is, a simplicial set with an added group structure compatible with the simplicial structure. Finally the fifth homology-group is asked for."
]
},
{
"cell_type": "code",
"execution_count": 169,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Computing boundary-matrix in dimension 5.\n",
"Rank of the source-module : 23.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 1/23 (dimension 5) :\n",
"<>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 2/23 (dimension 5) :\n",
"<>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 3/23 (dimension 5) :\n",
"<>][3 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 4/23 (dimension 5) :\n",
"<>][2 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 5/23 (dimension 5) :\n",
"<>]>>][4 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 6/23 (dimension 5) :\n",
"<>]>>][4 <>][3 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 7/23 (dimension 5) :\n",
"<>]>>][4 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 8/23 (dimension 5) :\n",
"<>]>>][3 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 9/23 (dimension 5) :\n",
"<>][2 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 10/23 (dimension 5) :\n",
"<>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 11/23 (dimension 5) :\n",
"<>][3 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 12/23 (dimension 5) :\n",
"<>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 13/23 (dimension 5) :\n",
"<>]>>][1 <>]>>][3 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 14/23 (dimension 5) :\n",
"<>]>>][2 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 15/23 (dimension 5) :\n",
"<>]>>][3 <>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 16/23 (dimension 5) :\n",
"<>]>>][1 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 17/23 (dimension 5) :\n",
"<>]>>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 18/23 (dimension 5) :\n",
"<>][2 <>]>>][1 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 19/23 (dimension 5) :\n",
"<>]>>][1 <>]>>][1 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 20/23 (dimension 5) :\n",
"<>]>>][1 <>]>>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 21/23 (dimension 5) :\n",
"<>]>>][2 <>]>>][1 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 22/23 (dimension 5) :\n",
"<>]>>][1 <>]>>][1 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 23/23 (dimension 5) :\n",
"<>]>>][1 <>]>>][1 <>]>>][1 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
"Computing boundary-matrix in dimension 6.\n",
"Rank of the source-module : 53.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 1/53 (dimension 6) :\n",
"<>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 2/53 (dimension 6) :\n",
"<>][5 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 16s.\n",
"Computing the boundary of the generator 3/53 (dimension 6) :\n",
"<>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 4/53 (dimension 6) :\n",
"<>][2 <>][3 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 5/53 (dimension 6) :\n",
"<>][3 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 6/53 (dimension 6) :\n",
"<>][2 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 7/53 (dimension 6) :\n",
"<>]>>][5 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 8/53 (dimension 6) :\n",
"<>]>>][5 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 9/53 (dimension 6) :\n",
"<>]>>][5 <>][3 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 10/53 (dimension 6) :\n",
"<>]>>][5 <>][2 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 11/53 (dimension 6) :\n",
"<>]>>][4 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 12/53 (dimension 6) :\n",
"<>]>>][4 <>][3 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 13/53 (dimension 6) :\n",
"<>]>>][4 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 14/53 (dimension 6) :\n",
"<>][2 <>]>>][3 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 15/53 (dimension 6) :\n",
"<>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 16/53 (dimension 6) :\n",
"<>][3 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 17/53 (dimension 6) :\n",
"<>][2 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 18/53 (dimension 6) :\n",
"<>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 19/53 (dimension 6) :\n",
"<>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 20/53 (dimension 6) :\n",
"<>][3 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 21/53 (dimension 6) :\n",
"<>][2 <>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 22/53 (dimension 6) :\n",
"<>]>>][1 <>]>>][4 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 23/53 (dimension 6) :\n",
"<>]>>][1 <>]>>][4 <>][3 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 24/53 (dimension 6) :\n",
"<>]>>][1 <>]>>][4 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 25/53 (dimension 6) :\n",
"<>]>>][2 <>]>>][3 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 26/53 (dimension 6) :\n",
"<>]>>][3 <>][2 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 27/53 (dimension 6) :\n",
"<>]>>][4 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 28/53 (dimension 6) :\n",
"<>]>>][4 <>][3 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 29/53 (dimension 6) :\n",
"<>]>>][4 <>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 30/53 (dimension 6) :\n",
"<>]>>][1 <>]>>][3 <>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 31/53 (dimension 6) :\n",
"<>]>>][2 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 32/53 (dimension 6) :\n",
"<>]>>][3 <>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 33/53 (dimension 6) :\n",
"<>][2 <>]>>][1 <>]>>][2 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 17s.\n",
"Computing the boundary of the generator 34/53 (dimension 6) :\n",
"<>][2 <>]>>][2 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 18s.\n",
"Computing the boundary of the generator 35/53 (dimension 6) :\n",
"<>]>>][1 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 18s.\n",
"Computing the boundary of the generator 36/53 (dimension 6) :\n",
"<>][3 <>]>>][1 <>]>>][1 <>]>>]>> \n",
"End of computing.\n",
"\n",
"\n",
";; Clock -> 2017-02-26, 2h 19m 18s.\n",
"Computing the boundary of the generator 37/53 (dimension 6) :\n",
"<