Misplaced Pages

Common Lisp Object System

Article snapshot taken from Wikipedia with creative commons attribution-sharealike license. Give it a read and then ask your questions in the chat. We can research this topic together.

The Common Lisp Object System (CLOS) is the facility for object-oriented programming in ANSI Common Lisp . CLOS is a powerful dynamic object system which differs radically from the OOP facilities found in more static languages such as C++ or Java . CLOS was inspired by earlier Lisp object systems such as MIT Flavors and CommonLoops , although it is more general than either. Originally proposed as an add-on, CLOS was adopted as part of the ANSI standard for Common Lisp and has been adapted into other Lisp dialects such as EuLisp or Emacs Lisp .

#401598

62-484: The basic building blocks of CLOS are methods , classes , instances of those classes, and generic functions . CLOS provides macros to define those: defclass , defmethod , and defgeneric . Instances are created with the method make-instance . Classes can have multiple superclasses , a list of slots (member variables in C++/Java parlance) and a special metaclass . Slots can be allocated by class (all instances of

124-475: A Circle and Ellipse to both be mutable container types, aliases of MutableContainer<ImmutableCircle> and MutableContainer<ImmutableEllipse> respectively. In this case, ImmutableCircle may be considered a subtype of ImmutableEllipse . The type T in MutableContainer<;T> can be both written to and read from, implying that it is neither covariant nor contravariant, and

186-484: A class rather than an instance. They are typically used as part of an object meta-model . I.e, for each class, defined an instance of the class object in the meta-model is created. Meta-model protocols allow classes to be created and deleted. In this sense, they provide the same functionality as constructors and destructors described above. But in some languages such as the Common Lisp Object System (CLOS)

248-458: A class , and objects are instances of a given class. One of the most important capabilities that a method provides is method overriding - the same name (e.g., area ) can be used for multiple different kinds of classes. This allows the sending objects to invoke behaviors and to delegate the implementation of those behaviors to the receiving object. A method in Java programming sets the behavior of

310-522: A metaobject protocol which allows generic functions to provide application specific specialization and dispatch rules. Dispatch in CLOS is also different from most OO languages: This dispatch mechanism works at runtime. Adding or removing methods thus may lead to changed effective methods (even when the generic function is called with the same arguments) at runtime. Changing the method combination also may lead to different effective methods. For example, Like

372-461: A Circle is-an Ellipse, consider the following analogous code. Now, a prisoner is obviously a person. So logically, a sub-class can be created: Also obviously, this leads to trouble, since a prisoner is not free to move an arbitrary distance in any direction, yet the contract of the Person class states that a Person can. Thus, the class Person could better be named FreePerson . If that were

434-448: A bank-account class provides a getBalance() accessor method to retrieve the current balance (rather than directly accessing the balance data fields), then later revisions of the same code can implement a more complex mechanism for balance retrieval (e.g., a database fetch), without the dependent code needing to be changed. The concepts of encapsulation and modularity are not unique to object-oriented programming. Indeed, in many ways

496-451: A circle which has become a de facto ellipse doesn't change type, then its type is a piece of information which is now out of date, reflecting the history of the object (how it was once constructed) and not its present reality (what it has since mutated into). Many object systems in popular use are based on a design which takes it for granted that an object carries the same type over its entire lifetime, from construction to finalization. This

558-512: A class object. For example, an object can send an area message to another object and the appropriate formula is invoked whether the receiving object is a rectangle , circle , triangle , etc. Methods also provide the interface that other classes use to access and modify the properties of an object; this is known as encapsulation . Encapsulation and overriding are the two primary distinguishing features between methods and procedure calls. Method overriding and overloading are two of

620-501: A class share the slot) or by instance. Each slot has a name and the value of a slot can be accessed by that name using the function slot-value . Additionally special generic functions can be defined to write or read values of slots. Each slot in a CLOS class must have a unique name. CLOS is a multiple dispatch system. This means that methods can be specialized upon any or all of their required arguments. Most OO languages are single-dispatch, meaning that methods are only specialized on

682-598: A collection of methods with a shared name and argument structure, each specialized for different arguments. Since Common Lisp provides non-CLOS classes for structures and built-in data types (numbers, strings, characters, symbols, ...), CLOS dispatch works also with these non-CLOS classes. CLOS also supports dispatch over individual objects (eql specializers). CLOS does not by default support dispatch over all Common Lisp data types (for example dispatch does not work for fully specialized array types or for types introduced by deftype ). However, most Common Lisp implementations provide

SECTION 10

#1732855713402

744-407: A mutable Ellipse object initialized using the circle's radius. From that point on, it is a separate object and can be mutated separately from the original circle without issue. Methods converting the other way need not commit to one strategy. For instance, there can be both Ellipse.minimalEnclosingCircle and Ellipse.maximalEnclosedCircle , and any other strategy desired. Then, wherever a circle

806-457: A new instance, rather than modifying the instance they act on. This means that it is no longer a problem to define Circle.stretchX , and the inheritance reflects the mathematical relationship between circles and ellipses. A disadvantage is that changing the value of an instance then requires an assignment , which is inconvenient and prone to programming errors, e.g., Orbit(planet[i]) := Orbit(planet[i]).stretchX A second disadvantage

868-476: A prisoner can not move an arbitrary distance in any direction and a person can is a wrong premise once again. Any object which is moving to any direction can encounter obstacles. The right way to model this problem would be to have a WalkAttemptResult walkToDirection(int meters, Direction direction) contract. Now, when implementing walkToDirection for the subclass Prisoner, you can check the boundaries and return proper walk results. One may, conceptually, consider

930-638: A similar purpose and function to destructors, but because of the differences between languages that utilize garbage-collection and languages with manual memory management, the sequence in which they are called is different. An abstract method is one with only a signature and no implementation body . It is often used to specify that a subclass must provide an implementation of the method, as in an abstract class . Abstract methods are used to specify interfaces in some programming languages. [REDACTED] The following Java code shows an abstract class that needs to be extended: The following subclass extends

992-407: A single diameter property, TwoDiameterFigure has two such properties (i.e., a major and a minor axis length). This strongly suggests that inheritance should never be used when the sub-class restricts the freedom implicit in the base class, but should only be used when the sub-class adds extra detail to the concept represented by the base class as in 'Monkey' is-an 'Animal'. However, stating that

1054-434: Is a central tenet of object-oriented analysis and design that subtype polymorphism , which is implemented in most object-oriented languages via inheritance , should be used to model object types that are subsets of each other; this is commonly referred to as the is-a relationship. In the present example, the set of circles is a subset of the set of ellipses; circles can be defined as ellipses whose major and minor axes are

1116-472: Is a similar solution to the above, but is slightly more powerful. Ellipse.stretchX now returns the new value of its X dimension. Now, Circle.stretchX can simply return its current radius. All modifications must be done through Circle.stretch , which preserves the circle invariant. If the interface contract for Ellipse states only that "stretchX modifies the X axis", and does not state "and nothing else will change", then Circle could simply force

1178-566: Is designing them both from scratch, then the author will be able to define the interface to handle this situation. If the Ellipse object was already written, and cannot be changed, then the options are more limited. Allow the objects to return a "success" or "failure" value for each modifier or raise an exception on failure. This is usually done in the case of file I/O, but can also be helpful here. Now, Ellipse.stretchX works, and returns "true", while Circle.stretchX simply returns "false". This

1240-404: Is in general good practice, but may require that the original author of Ellipse anticipated such a problem, and defined the mutators as returning a value. Also, it requires the client code to test the return value for support of the stretch function, which in effect is like testing if the referenced object is either a circle or an ellipse. Another way to look at this is that it is like putting in

1302-502: Is just a special case of overloading where the selection is based only on the first argument. The following simple Java example illustrates the difference: Accessor methods are used to read the data values of an object. Mutator methods are used to modify the data of an object. Manager methods are used to initialize and destroy objects of a class, e.g. constructors and destructors. These methods provide an abstraction layer that facilitates encapsulation and modularity . For example, if

SECTION 20

#1732855713402

1364-413: Is mostly obsolete, though implementations for Common Lisp do exist. Flavors was using the message passing paradigm. New Flavors introduced generic functions. CommonLoops was the successor of LOOPS (from Xerox Interlisp -D). CommonLoops was implemented for Common Lisp. A portable implementation called Portable CommonLoops (PCL) was the first implementation of CLOS. PCL is widely ported and still provides

1426-472: Is not a limitation of OOP, but rather of particular implementations only. The following example uses the Common Lisp Object System (CLOS) in which objects can change class without losing their identity. All variables or other storage locations which hold a reference to an object continue to hold a reference to that same object after it changes class. The circle and ellipse models are deliberately simplified to avoid distracting details which are not relevant to

1488-480: Is only allowed on instances satisfying Ellipse.stretchable , and will otherwise throw an exception . This requires anticipation of the problem when Ellipse is defined. Create an abstract base class called EllipseOrCircle and put methods that work with both Circle s and Ellipse s in this class. Functions that can deal with either type of object will expect an EllipseOrCircle , and functions that use Ellipse - or Circle -specific requirements will use

1550-475: Is that such an assignment conceptually involves a temporary value, which could reduce performance and be difficult to optimise. One can define a new class MutableEllipse , and put the modifiers from Ellipse in it. The Circle only inherits queries from Ellipse . This has a disadvantage of introducing an extra class where all that is desired is specify that Circle does not inherit modifiers from Ellipse . One can specify that Ellipse.stretchX

1612-489: The Ellipse class to be an acceptable replacement for Circle . For languages that allow implicit conversion like C++ , this may only be a partial solution solving the problem on call-by-copy, but not on call-by-reference. One can change the model so that instances of the classes represent constant values (i.e., they are immutable ). This is the implementation that is used in purely functional programming. In this case, methods such as stretchX must be changed to yield

1674-404: The de facto norm in most Lisp dialect implementations, as well as finding their way into some other languages' OOP facilities: Method (computer programming) A method in object-oriented programming (OOP) is a procedure associated with an object , and generally also a message . An object consists of state data and behavior ; these compose an interface , which specifies how

1736-635: The square–rectangle problem ) illustrates several pitfalls which can arise when using subtype polymorphism in object modelling . The issues are most commonly encountered when using object-oriented programming (OOP). By definition, this problem is a violation of the Liskov substitution principle , one of the SOLID principles. The problem concerns which subtyping or inheritance relationship should exist between classes which represent circles and ellipses (or, similarly, squares and rectangles ). More generally,

1798-540: The ANSI Common Lisp standard, there is a widely implemented extension to CLOS called the Metaobject Protocol (MOP). The MOP defines a standard interface to the underpinnings of the CLOS implementation, treating classes, slot-descriptions, generic-functions and methods themselves as instances of metaclasses , and allows the definition of new metaclasses and the modification of all CLOS behavior. The flexibility of

1860-529: The CLOS MOP prefigures aspect-oriented programming , which was later developed by some of the same engineers, such as Gregor Kiczales . The MOP defines the behavior of the whole object system by a set of protocols. These are defined in terms of CLOS. Thus it is possible to create new object-systems by extending or changing the provided CLOS functionality. The book The Art of the Metaobject Protocol describes

1922-600: The OO systems in most dynamic languages , CLOS does not enforce encapsulation . Any slot can be accessed using the slot-value function or via (optionally auto-generated) accessor methods . To access it via slot-value you have to know the name of the slot. CL programmers use the language's package facility to declare which functions or data structures are intended for export. Apart from normal ("primary") methods, there also are :before , :after , and :around "auxiliary" methods. The former two are invoked prior to, or after

Common Lisp Object System - Misplaced Pages Continue

1984-562: The X and Y dimensions to be the same. Circle.stretchX and Circle.stretchY both modify both the X and Y size. If Circle.stretchX is called, then Circle changes itself into an Ellipse . For example, in Common Lisp , this can be done via the CHANGE-CLASS method. This may be dangerous, however, if some other function is expecting it to be a Circle . Some languages preclude this type of change, and others impose restrictions on

2046-594: The average price of all products. A static method can be invoked even if no instances of the class exist yet. Static methods are called "static" because they are resolved at compile time based on the class they are called on and not dynamically as in the case with instance methods, which are resolved polymorphically based on the runtime type of the object. In Java, a commonly used static method is: This static method has no owning object and does not run on an instance. It receives all information from its arguments. Copy-assignment operators define actions to be performed by

2108-410: The base for the CLOS implementation of several Common Lisp implementations. PCL is implemented mostly in portable Common Lisp with only a few system dependent parts. Because of the power and expressivity of CLOS, as well as the historical availability of Tiny CLOS (a simplified portable CLOS implementation written by Gregor Kiczales for use with Scheme), CLOS-like MOP-based object systems have become

2170-448: The benefits of object-oriented development. Perhaps the most well-known example is C++ , an object-oriented extension of the C programming language. Due to the design requirements to add the object-oriented paradigm on to an existing procedural language, message passing in C++ has some unique capabilities and terminologies. For example, in C++ a method is known as a member function . C++ also has

2232-510: The case, then the idea that class Prisoner extends FreePerson is clearly wrong. By analogy, then, a Circle is not an Ellipse, because it lacks the same degrees of freedom as an Ellipse. Applying better naming, then, a Circle could instead be named OneDiameterFigure and an ellipse could be named TwoDiameterFigure . With such names it is now more obvious that TwoDiameterFigure should extend OneDiameterFigure , since it adds another property to it; whereas OneDiameterFigure has

2294-476: The circle–ellipse problem. An ellipse has two semi-axes called h-axis and v-axis in the code. Being an ellipse, a circle inherits these, and also has a radius property, which value is equal to that of the axes (which must, of course, be equal to each other). This code can be demonstrated with an interactive session, using the CLISP implementation of Common Lisp. While at first glance it may seem obvious that

2356-418: The class membership of a given instance through the change-class operator. CLOS also allows one to add, redefine and remove methods at runtime. The Circle-Ellipse Problem is readily solved in CLOS, and most OOP design patterns either disappear or are qualitatively simpler. CLOS is not a prototype language : classes must be defined before objects can be instantiated as members of that class. Outside of

2418-421: The compiler generates code to call them at appropriate times. Static methods are meant to be relevant to all the instances of a class rather than to any specific instance. They are similar to static variables in that sense. An example would be a static method to sum the values of all the variables of every instance of a class. For example, if there were a Product class it might have a static method to compute

2480-409: The compiler when a class object is assigned to a class object of the same type. Operator methods define or redefine operator symbols and define the operations to be performed with the symbol and the associated method parameters. C++ example: Some procedural languages were extended with object-oriented capabilities to leverage the large skill sets and legacy code for those languages but still provide

2542-463: The concept of virtual functions which are member functions that can be overridden in derived classes and allow for dynamic dispatch . Virtual functions are the means by which a C++ class can achieve polymorphic behavior. Non-virtual member functions , or regular methods , are those that do not participate in polymorphism . C++ Example: Circle-ellipse problem The circle–ellipse problem in software development (sometimes called

Common Lisp Object System - Misplaced Pages Continue

2604-498: The contract that the contract may or may not be fulfilled depending on the object implementing the interface. Eventually, it is only a clever way to bypass the Liskov constraint by stating up-front that the post condition may or may not be valid. Alternately, Circle.stretchX could throw an exception (but depending on the language, this may also require that the original author of Ellipse declare that it may throw an exception). This

2666-453: The default order in which methods are executed in multiple inheritance is not correct, the programmer may resolve the diamond inheritance problems by specifying the order of method combinations. CLOS is dynamic, meaning that not only the contents, but also the structure of its objects can be modified at runtime. CLOS supports changing class definitions on-the-fly (even when instances of the class in question already exist) as well as changing

2728-445: The descendant classes. However, Circle is then no longer an Ellipse subclass, leading to the "a Circle is not a sort of Ellipse " situation described above. This solves the problem at a stroke. Any common operations desired for both a Circle and Ellipse can be abstracted out to a common interface that each class implements, or into mixins . Also, one may provide conversion methods like Circle.asEllipse , which returns

2790-430: The first argument. Another unusual feature is that methods do not "belong" to classes; classes do not provide a namespace for generic functions or methods. Methods are defined separately from classes, and they have no special access (e.g. "this", "self", or "protected") to class slots. Methods in CLOS are grouped into generic functions . A generic function is an object which is callable like a function and which associates

2852-526: The following example in Java: A Destructor is a method that is called automatically at the end of an object's lifetime, a process called Destruction . Destruction in most languages does not allow destructor method arguments nor return values. Destructors can be implemented so as to perform cleanup chores and other tasks at object destruction. In garbage-collected languages, such as Java , C# , and Python , destructors are known as finalizers . They have

2914-425: The implementation. An ellipse requires more states to be described than a circle, because the former needs attributes to specify the length and rotation of the major and minor axes whereas a circle needs only a radius. It may be possible to avoid this if the language (such as Eiffel ) makes constant values of a class, functions without arguments, and data members interchangeable. Some authors have suggested reversing

2976-408: The internals of the object can be made with minimal impact on the other objects that use it. This is known as encapsulation and is meant to make code easier to maintain and re-use. Method overloading, on the other hand, refers to differentiating the code used to handle a message based on the parameters of the method. If one views the receiving object as the first parameter in any method then overriding

3038-428: The length of one of its axes in place. If Circle inherits from Ellipse , it must also have a method stretchX , but the result of this method would be to change a circle into something that is no longer a circle. The Circle class cannot simultaneously satisfy its own invariant and the behavioural requirements of the Ellipse.stretchX method. A related problem with this inheritance arises when considering

3100-508: The main class: If a subclass provides an implementation for an abstract method, another subclass can make it abstract again. This is called reabstraction . In practice, this is rarely used. In C#, a virtual method can be overridden with an abstract method. (This also applies to Java, where all non-private methods are virtual.) Interfaces' default methods can also be reabstracted, requiring subclasses to implement them. (This also applies to Java.) Class methods are methods that are called on

3162-500: The meta-model allows the developer to dynamically alter the object model at run time: e.g., to create new classes, redefine the class hierarchy, modify properties, etc. Special methods are very language-specific and a language may support none, some, or all of the special methods defined here. A language's compiler may automatically generate default special methods or a programmer may be allowed to optionally define special methods. Most special methods cannot be directly called, but rather

SECTION 50

#1732855713402

3224-419: The methods must be explicitly inherited. The model can be emulated in languages with multiple inheritance , using abstract classes . This problem has straightforward solutions in a sufficiently powerful OO programming system. Essentially, the circle–ellipse problem is one of synchronizing two representations of type: the de facto type based on the properties of the object, and the formal type associated with

3286-410: The most significant ways that a method differs from a conventional procedure or function call. Overriding refers to a subclass redefining the implementation of a method of its superclass. For example, findArea may be a method defined on a shape class, triangle , etc. would each define the appropriate formula to calculate their area. The idea is to look at objects as "black boxes" so that changes to

3348-466: The object by the object system. If these two pieces of information, which are ultimately only bits in the machine, are kept synchronized so that they say the same thing, everything is fine. It is clear that a circle cannot satisfy the invariants required of it while its base ellipse methods allow mutation of parameters. However, the possibility exists that when a circle cannot meet the circle invariants, its type can be updated so that it becomes an ellipse. If

3410-416: The object may be used. A method is a behavior of an object parametrized by a user. Data is represented as properties of the object, and behaviors are represented as methods. For example, a Window object could have methods such as open and close , while its state (whether it is open or closed at any given point in time) would be a property. In class-based programming , methods are defined within

3472-467: The object-oriented approach is simply the logical extension of previous paradigms such as abstract data types and structured programming . A constructor is a method that is called at the beginning of an object's lifetime to create and initialize the object, a process called construction (or instantiation ). Initialization may include an acquisition of resources. Constructors may have parameters but usually do not return values in most languages. See

3534-615: The primary method, in a particular order based on the class hierarchy. An :around method can control whether the primary method is executed at all. Additionally, the programmer can specify whether all possible primary methods along the class hierarchy should be called or just the one providing the closest match. The Standard Method-Combination provides the primary, before, after and around methods explained above. There are other Method-Combinations with other method types. New (both simple and complex) Method-Combinations and method types can be defined. CLOS allows multiple inheritance . When

3596-521: The problem illustrates the difficulties which can occur when a base class contains methods which mutate an object in a manner which may invalidate a (stronger) invariant found in a derived class, causing the Liskov substitution principle to be violated. The existence of the circle–ellipse problem is sometimes used to criticize object-oriented programming. It may also imply that hierarchical taxonomies are difficult to make universal, implying that situational classification systems may be more practical. It

3658-412: The relationship between circle and ellipse, on the grounds that an ellipse is a circle with more abilities. Unfortunately, ellipses fail to satisfy many of the invariants of circles; if Circle has a method radius , Ellipse must now provide it, too. One may solve the problem by: Exactly which option is appropriate will depend on who wrote Circle and who wrote Ellipse . If the same author

3720-407: The same length. Thus, code written in an object-oriented language that models shapes will frequently choose to make class Circle a subclass of class Ellipse , i.e. inheriting from it. A subclass must provide support for all behaviour supported by the super-class; subclasses must implement any mutator methods defined in a base class. In the present case, the method Ellipse.stretchX alters

3782-669: The use and implementation of the CLOS MOP. The various Common Lisp implementations have slightly different support for the Meta-Object Protocol. The Closer project aims to provide the missing features. Flavors (and its successor New Flavors) was the object system on the MIT Lisp Machine . Large parts of the Lisp Machine operating systems and many applications for it use Flavors or New Flavors. Flavors introduced multiple inheritance and mixins , among other features. Flavors

SECTION 60

#1732855713402

3844-570: Was used before, use an ellipse. A circle can already be represented by an ellipse. There is no reason to have class Circle unless it needs some circle-specific methods that can't be applied to an ellipse, or unless the programmer wishes to benefit from conceptual and/or performance advantages of the circle's simpler model. Majorinc proposed a model that divides methods on modifiers, selectors and general methods. Only selectors can be automatically inherited from superclass, while modifiers should be inherited from subclass to superclass. In general case,

#401598