An Introduction to Tingle Tingle is the first idea oriented programming language. As object oriented programs have classes as their basic components, idea oriented programs have ideas, named after Platonic Ideas. Ideas do not build on other ideas, as classes extend other classes; instead they are independent, but they click together when used in combination, without any need for traits-style glue. Through this property, they can also serve as classes, as mixins, even as dynamically activated layers (as in context oriented programming). The secret sauce is one single innovation: the emancipation of the lexical scoping of methods from the composition of classes. Traditionally, composition determines scoping: for instance, subclassing adds new components to a class, which can override other components and have them in scope; and similarly, in a language with mixins, the order in which they are combined creates scoping; but here the roles are reversed, and scoping determines composition. This introduction to Tingle should serve as proof that idea oriented programming is actually doable, as well as possible. In particular it should show that Tingle code is neat and concise, and that the language encourages separation of concerns. It will therefore present the beginnings of a programming style that agrees with the language, while not looking too foreign to object oriented programmers. The first three sections cover the basics: after we have explained the difference between ideas and classes in section 1, section 2 introduces the concept of "top method", and section 3 explains scoping in detail. The next three sections are about programming in the small: section 4 compares composition with traditional subclassing and gives some pointers about programming style, section 5 goes into the relationship between ideas and mixins, and section 6 introduces simple templates. Then we need to say something about programming in the large. Section 7 introduces name spaces, which allow us to reuse library components as black boxes, either in isolation or with mixins. Section 8 refines the scoping rule, which additionally allows us to bring library components in scope as black boxes, so that we can emulate subclassing again, if we want to. The next two sections adapt well-known features from other languages to Tingle. Section 9 explains how the concepts of "public", "private" and "protected" fit in, and adds an extra category. Section 10 adapts Scala companion objects. Section 11 wraps up with some general remarks. There are two appendices, which apply specifically to Tingle, not to idea oriented programming in general. Appendix A demonstrates some dynamic features of the language. Appendix B builds on that to show that ideas can also be used as layers for context oriented programming. 1 Classes and Ideas In an imaginary language, that looks a lot like Tingle, let us define a simple class Human as follows: class Human { def play_tetris() { ... } } Now to derive a subclass Woman, providing some specifically female skills, we would, perhaps using some specialized syntax like "extends", reopen the scope of Human, and embed the class Woman inside it, so that the methods and data of the class Human would be visible for any newly added methods for Woman. Staying as close as possible to Tingle syntax, we would write something like: class Human { class Woman { def give_birth() { ... } def walk_on_high_heels() { ... } } } The two new methods are able to see the Human method play_tetris(), which is in an enclosing scope. The above code is in fact syntactically valid in Tingle; but it means that the classes Human and Woman overlap, and that the two new methods exist in their set intersection, available only to objects that are both Human and Woman. As all women are by definition also human, the class Woman and the intersection coincide. Maybe we can make the example more interesting by making it about the intersection of Human and Female instead: class Human { class Female { def give_birth() { ... } def walk_on_high_heels() { ... } } } class Woman = Human Female. We now have two independent classes, Human and Female, neither of which is a superclass or a subclass of the other; and therefore we needed to separately declare the composite class Woman, modeling beings that are both Human and Female. To distinguish between these two kinds of classes, we call Human and Female ideas, and only Woman will be a class: the name "idea" (imperfectly) reflects Platonic jargon, where "a woman takes part in the Ideas Human and Female", instead of being Human with subclass Woman. Also, to highlight the difference between classes and ideas, we will preferably open blocks of ideas with the specialized keyword "as". Here we should point out, that classes in Tingle are no more than collections of ideas: they have no properties except the properties that they derive from the ideas they contain. For any collection of ideas, there will be only one way in which they "click together", so that the declaration of the class Woman did not need any further annotations or glue code, nor was even the order of Human and Female relevant. The next two sections together will explain how this works. 2 The Top Method Next let us make the idea Female reusable. As order is irrelevant for set intersection, we start by swapping Human and Female: as Female { as Human { def buy_more_shoes() { ... } def give_birth() { ... } } } We can now express something that we could not express earlier: the fact that give_birth() applies to many kinds of females, while walking on high heels is a skill specific to human females: as Female { def give_birth() { ... } as Human { def walk_on_high_heels() { ... } } } As might be expected, a more specialized method give_birth() for human females could be added in the intersection. It might then involve playing Tetris (although this seems highly unlikely). If we would create an object of a class that consists only of the idea Female, it would provide the basic method give_birth(), and no walk_on_high_heels() at all. If we would create an object of the class Woman, on the other hand, all the most specialized methods would be available. Methods in the intersection of Female and Human would be preferred over methods with the same name in the separate ideas. For a Woman named mary, mary.play_tetris() would refer to a method in the intersection if a method of that name was available there; if not, it would refer to a method in either Human or Female. As there is no hierarchic relationship between Human and Female, when both the ideas Human and Female would happen to define a method with the same name, and the intersection did not resolve the conflict, neither version of the method would win out, and it would be an error to send a message of that name to the object. More generally, for a class that combines several ideas in which a same method name occurs several times, the top method, as we will call it, is the method of that name in the smallest intersection that encompasses all the ideas and intersections of ideas that define that method, if such a method exists at that place. If not, there is no top method. ("More specialized than" is not a total order.) Now let us reuse the idea Female in the animal kingdom: as Bovine { def look_at_passing_trains() { ... } } class Cow = Female Bovine. For a Cow, only the part of Female outside of the intersection with Human is relevant: the intersection with Human is simply ignored in composing this new class. The intersection of Bovine and Female on the other hand is still empty, so let us now add a method moo(): as Bovine { def look_at_passing_trains() { ... } as Female { def moo() { ... } } } The method moo() characterizes Bovine rather than Female -- I personally associate the concept of mooing with bovines rather than with femininity -- and that is why I add moo() to the definition of Bovine, further nested in Female, rather than the other way around. I opened a new block for the idea Female in this example, away from its original definition: ideas can be reopened as often as needed, as an outer nesting as well as nested in others. I could also have used a new Bovine-block, but instead chose to add to the one from the previous example. The two methods that are now inside the same Bovine-block seem to go well together thematically. When one arranges methods thematically by reopening ideas several times, tools can be provided to view the code by complete ideas, or complete classes, or to see all variables in a certain scope together, to help understanding. Such a tool, called the Idea Browser, is still in the vaporware stage, but it is not intrinsically hard to make one. Tools for the opposite direction, that could rearrange code of traditional classes by theme or concern, would be harder to produce. 3 Scoping Top methods are concerned with the messages sent between objects. The mechanism is flexible: different classes that incorporate one same idea may have different top methods for the same method names. But at a lower level, such flexibility would be undesirable. If we reference an instance variable in a method in an idea, we want to be able to see at that level of the source code, which variable of that name we are referring to. If this would vary with which classes the idea would later be made part of, ideas would be very hard to reuse. (They would be like mixins, but without even the benefit of a mixing order.) That is why variable access is regulated by traditional lexical scope. The same goes for method lookup when methods are called from within the same object -- that is, as a function call, as in "method()", not by sending a message, as in "mary.method()" or "self.method()". In the example in which we swapped Human and Female, the actual methods were not shown. But as soon as we look at the method bodies, the order of nesting ideas does matter. So section 2 was a bit misleading, but as it will turn out, this does not matter in practice. The examples in section 4 will illustrate that you get concise and coherent blocks of code if you nest in such a manner that the ideas that prompt more specialized versions of the methods you are working on are nested deeper. Which meshes well with lexical scoping. Methods in an intersection can access only variables from ideas that make up the intersection. There are no free variables that get caught at composition time: the order of nesting around a method definition determines locally which variable an identifier refers to in that method. To revisit our example one last time: as Female { var children } as Bovine { as Female { def moo() { if (children > 3) ... } } } Around moo(), the inner scope is formed by the intersection of Female and Bovine; the next scope by Female only; and the next by Bovine only. The variable children in the idea Female is not in the same block, but it is in scope. (This is where the Idea Browser will come in handy.) If the variable children had not been declared in Female, but in Human, moo() would not work, not even for the goddess Isis: class Isis = Female Bovine Human. The method moo() would not see the variable children in the idea Human, even though it would be present in the class Isis. So scope avoids surprises. Using the notation idea::variable the programmer can explicitly override the scoping order, but not refer to an idea outside of the scope. In moo() the distinction between Female::children and Bovine::children could be made, but trying to use Human::children would be an error, as Human is not in scope. To explicitly refer to a variable or method in an intersection, use constructions like Female::Bovine::moo(). (Order is unimportant.) In our moo(), super.moo() will refer to Female::moo() if that exists; if not, Bovine::moo() is tried. Super calls are function calls, not messages, so they are also resolved through scoping. For methods defined only two levels deep, I think everyone except the most quarrelsome will accept this scoping rule as the obvious and "correct" one. The complete rule as used in Tingle however is only one of the possible generalizations of the obvious one for two levels. It is rather complex: it was chosen because it assures that intersections always have precedence over their separate ideas, and also that inner scopes have precedence over outer ones. I will explain it from an abstract example: as A { as B { ... as Y { as Z { def myMethod() { print(v) } ... } The variable v will first be looked for in the intersection of all ideas A through Z (where it would reside if it was declared in the same nesting as myMethod), then in the smaller intersections B through Z, C through Z etc. When the intersection Z through Z is reached, i.e. the idea Z alone, and nothing has been found, then we continue with the intersections A through Y until only Y, then the intersections A through X until only X etc. This way all continuous intersections (like C+D+E and P+Q+R+S+T) will be searched; intersections are always searched before the single ideas that make them up, and also before intersections of only some of the same ideas; and inner ideas before outer ones. If you would want to use a variable or method that is in a non-continuous intersection, you can still use the ::-notation, but I suspect that this really means you need to restructure your code. 4 Programming Style If the previous section leaves you with the impression that scoping for methods many levels deep will be awfully complex, this section is all about avoiding deep nestings. That does help in understanding programs; however the real reason to avoid nesting wherever possible is to promote reusability. (Every nesting is a dependency.) We can emulate boring old single inheritance in Tingle: as car { def start() { ... } def stop() { ... } } as car { as taxi { def answer_call() { ... stop() ... start() ... } } } class Car = car. class Taxi = taxi car. This is all very well, but emulating multiple inheritance would lead to overly deep nesting: note that we only nest with ideas, not with classes. (Classes have no internal order; and if they would have one, that one would probably not always be the appropriate one.) A "literal translation" of code with multiple inheritance would therefore be ugly, and its scoping complex: as car { as carGUIObject { def draw() { ... } } } as car { as carGUIObject { // or should taxi come first ? as taxi { as taxiGUIObject { def draw() { ... super.draw() ... } } } } } class CarGUIObject = car carGUIObject. class TaxiGUIObject = taxi car taxiGUIObject carGUIObject. There is a better way, with shallow scoping, and all draw() methods snugly together in the source code; you do this by introducing only one new idea for the concern "drawing": as GUIdraw { as car { def draw() { ... } as taxi { def draw() { ... super.draw() ... } } } } class CarGUIObject = GUIdraw Car. class TaxiGUIObject = GUIdraw Taxi. // equivalent to GUIDraw taxi car This shows that there is no dominant decomposition by the sort of the vehicle, and also that the "diamond problem" does not exist here. Note that, if we want to add another concern later, we can write this up without taking GUIdraw in scope, when the two concerns are independent of each other: as accounting { as car { def costs() { ... } as taxi { def costs() { ... } } } as employee { ... } } Again, scoping is shallow. We can compose all this with: class CompanyCar = accounting GUIdraw Car. class CompanyTaxi = accounting GUIdraw Taxi. All these good things come with a trade-off: in a system based on intersecting ideas, emulating "subclassing for construction" or "subclassing for combination" is the way madness lies. The relationship between a class and its parts should always be like "is a" or "is": never "has a" or "is a bit like a". (Use an instance variable to express "has a" relationships.) We conclude this section with another, more superficial example of programming style: a rather aesthetically pleasing way to write what in traditional object oriented thinking would be ugly subclasses that are smaller than their superclass: class Ellipse = generic ellipse. as ellipse { var colour, axis as generic { var second_axis def area() { ... } } } Ellipse is written in this manner so that later we can easily and correctly derive: class Circle = circular ellipse. as ellipse { as circular { def area() { ... } } } 5 Mixins Note again the contrast between the rule from section 2 to determine the top method in a class, and the scoping rule from section 3. The first is for when an object is accessed "from the outside", with "object.methodname()"; the second is for accesses of methods or variables inside an object, with "methodname()" or "variablename" only. ("Object.variablename" is not allowed; if it were, it would be the "top variable".) "Virtual" function calls, to abuse C++ terminology, as well as mixin- like behaviour can be obtained by "stepping out of the scope" by using "self.methodname()" instead of "methodname()". Note that this "virtuality" here is a property of the call, not of the method itself. In Tingle there is no technical difference between mixins and ideas, but the name mixin would typically fit an idea without any intersections, which accesses other methods of the same object through "self", so that it can be mixed into any class that provides the necessary methods. Therefore we recognize the following code snippet as a mixin. It can be mixed into classes that define isEqual and isLess: as ordered { def isGreater(x) { ! (self.isEqual(x) || self.isLess(x)) } def isUnequal(x) { ! self.isEqual(x) } } Lacking nesting, the source code of this mixin does not show what ideas it should or should not be mixed with, nor what kind of object "self" will then refer to. As a matter of fact, in the dynamic language that Tingle is, we can mix Ordered into classes that do not provide isEqual and isLess, and so compose broken code. For a very general mixin like this one, all this is not a real problem; but often templates with clean scoping are preferable above mixins. 6 Templates These are two independent ideas, without any intersections: as tapereader { def read() { ... } } as diskreader { def read() { ... } } And this is how we extend them together: as buffered { as tapereader, diskreader { def read() { ... super.read() ... } } } class BufferedTapereader = buffered tapereader. class BufferedDiskreader = buffered diskreader. Which is short for: as buffered { as tapereader { def read() { ... super.read() ... } } as diskreader { def read() { ... super.read() ... } } } Syntax as used in the examples until here does however not allow to express the following as a template: as buffered { as diskreader { as floppy { def read() { ... super.read() ... } } } as tapereader { def read() { ... super.read() ... } } } So we add an alternative syntax for nestings that only consist of an intersection: as buffered { as diskreader->floppy { def read() { ... super.read() ... } } as tapereader { def read() { ... super.read() ... } } } And then we can write the template: as buffered { as tapereader, diskreader->floppy { def read() { ... super.read() ... } } } Note how the arrow syntax is also suitable for expressing traditional subclassing concisely: class car->taxi { ... } class Taxi = taxi car. 7 Name spaces The language as presented up to here does not scale to projects that use libraries or frameworks or in any way reuse code written by someone else. We still need to introduce a mechanism by which any reusable body of code can be used through a small interface, without exposing all its internals. A set of classes, which export only their top methods, could serve as such an API; but the ideas out of which they consist should be hidden. Simple name spaces are enough to arrange this. We can put a library in a separate name space and refer to its classes using fully qualified names: w = new Widget@WidgetLibrary Alternatively, we can import the class Widget into the current name space using syntax that we already know: class Widget = Widget@WidgetLibrary. w = new Widget In both cases, we can use a Widget without worrying whether some of the ideas that make up the class Widget have the same names as some of our own ideas: these ideas are still in another name space. (You can actually refer to ideas by fully qualified names too, at your own risk: name spaces hide ideas and classes, but do not make them inaccessible to the determined hacker. The point is rather that there are no or very few good reasons to do this.) We are not limited to using library classes as they are: we can also extend them with our own mixins: namespace main. as myWidget { ... } class MyWidget = Widget@WidgetLibrary myWidget. The class MyWidget consists of all the ideas of Widget plus our own idea myWidget. If one of the ideas out of which Widget is composed were also named myWidget, it would still be separate from our own myWidget, as it lives in the same class but in a different name space. What we cannot do, is nest myWidget inside the ideas that make up Widget, as we are not supposed to know about them. So myWidget is necessarily a mixin. The next section will present a new way to use nesting, which alleviates this problem without throwing away the benefits of name spaces. First two final remarks about name spaces. One, name spaces are easy to use correctly: pieces of code that were not designed together should always be in separate name spaces. All class and idea names in a Tingle program are implicitly qualified with the current name space, except if a name space is explicitly specified; so within the default name space "main", any reference to an idea named myWidget will be interpreted as myWidget@main. Two, you might wonder whether name spaces might be equivalent to an extra idea around all the code that is in them. They are not, because they do not exist in isolation. They have no content of their own and you have no choice whether to include them in a class or not. Instead, name spaces can be thought of as simply a mechanism for rewriting idea and class names, adding suffixes like "@WidgetLibrary". 8 Your Class is my Idea In the previous section we hid ideas in a name space, and accessed only classes. Without access to the ideas, we could not use nesting, so that we could only use the classes in isolation and with mixins. Our next step is that we make the API that is offered by the classes available for scoping again, so that we can emulate subclassing from a library class, for instance. To enumerate the drawbacks of using only name spaces: we can now use Widget@WidgetLibrary as a black box, which exports its top methods as an API, but when using this safe API in the mixin myWidget: - we have to write "self." all the time; - we lose coupling: the source code of the mixin does not specify which one specific other class it is meant as a mixin for (when it is in fact a hack for a subclass); - most importantly, when the mixin and the old class provide methods with the same name, no new top method can be added to resolve the conflict, as the classes do not intersect (an important issue if what you actually wanted was a subclass). Therefore we introduce the possibility to regard classes as ideas when used outside of their own native name space, and so use them for nesting: as Widget@WidgetLibrary { as myWidget { ... } } class MyWidget = Widget@WidgetLibrary myWidget. (To be excruciatingly precise, Widget would actually be allowed to be in the current name space, but not Widget's ideas.) In this example, for the scoping rule, the class Widget@WidgetLibrary will be considered to be an idea without data members, and with as methods only the top methods of the class. This corresponds with the viewpoint from a mixin, except that: - methods of Widget can be called without "self."; - coupling is explicit: if Widget is not part of a newly composed class, then the intersection with myWidget will not be either, and no broken code will be present in the system; - the methods of Widget are further from the "top" than those in the new intersection with myWidget; - one can also refer to Widget using "super." and the ::-notation to directly access its top methods. The analogy between ideas from your own name space and classes from other name spaces is not perfect. Classes used as ideas are self-contained for scoping: if they are nested themselves, their methods do not suddenly get the enclosing nestings in scope, as they were not defined in them. (Note that classes imported from the same name space can share data; but this is done through scoping in that other name space, so this is the responsibility of the library writer, who should know that code intimately.) Each nesting should contain at least one "real" idea of your own. This is not currently enforced, but for instance the effect of adding code directly in a class used as an idea is left unspecified: as Widget@WidgetLibrary { // the semantics of adding code here has not been settled } In conclusion, Tingle does not only support a novel very open style in code that belongs and is designed together, but also provides extra hiding and encapsulation when we cross the boundaries of such modules; to fall back to what is essentially multiple inheritance with sharing of common parts. 9 Access Control The meaning of such concepts as public, private and protected members can be adapted from languages in the C++ tradition: in Tingle private means "not accessible through scoping" and protected means "not accessible through message sends", which is pretty much what they mean in C++ too. Note that private did not have to imply protected in Tingle; but it seemed to make sense to make this so anyway. Scoped private annotations as in Scala would seem like a perfect fit for Tingle, but they are not implemented. In particular "private to the current nesting and everything lexically nested in it" seems promising (although it could use a snappier name). By default all data is protected, i.e. unreachable from outside of the object; and all methods are public. This can be modified using: private var ... protected def ... private def ... The exact meaning of private is: not accessible through scoping from outside of the current idea or the current intersection of ideas. The exact meaning of protected, also as implied by private, is: not accessible for message sends, and also hiding all methods "under" this one when the top method is to be determined. There is a special form of "protected" that is ignored for direct self sends: semiprotected def ... // or shorter: semi def ... Direct self sends are expressions of the syntax "self.msg(...)", as opposed to sends to other expressions that happen to evaluate to be identical to the sending object; and also the calling of top methods through scoping as described in section 8, which are self sends by another name. 10 Companion Objects The Tingle interpreter allows variable and method declarations at top level, outside of any nesting. These are stored in a singleton object named "*lobby*". The class of the object "*lobby*" has no name and is part of no name space. The "*lobby*" object responds to no messages. But the contents of "*lobby*" are always in scope: the outermost scope, just before the companion objects and the primitive functions. Such top level entities are great for quick experiments at the prompt, but not meant to hold global entities in a program. For the latter purpose, companion objects are the clean alternative. Each class has a companion object associated with it: an object with the same name as the class, accessed through a scope even outside of that of the top level variables. As opposed to top level declarations, which like the primitive functions reside in all name spaces at once, companion objects do reside in the name space of their associated class. Companion objects are explicit containers for methods and values that belong with a class, but not with a specific instance of it; more or less like class methods and class variables in some languages. We need only one extra keyword to declare them: as bankAccount { var amountOfMoney } in bankAccount { var amountOfAccounts } "In" blocks can be nested in other "in" blocks in just the same way as "as" blocks can be nested in other "as" blocks; and as a convenience, they can be nested together in "at" blocks. ("At" blocks can only be nested in other "at" blocks, and can only contain further nestings, no declarations of their own.) Let us say we compose: class BankAccount = bankAccount. Then not only is the class BankAccount created out of the "as" part of bankAccount; but a second, nameless class (refered to as ~BankAccount in error messages) is created out of the "in" part, and then a singleton object of that latter class is created under the name BankAccount. So there will now be one instance of the variable "amountOfAccounts", part of the object BankAccount; and more generally only one per class that includes the idea bankAccount. Companion objects have privileged access to the instances of their associated class; and instances have privileged access to the companion object of their class. Message sends between them are considered equivalent to direct self sends, making semi-protected methods of the companion object accessible to instances, and semi-protected methods of instances accessible to companion objects. The latter property makes companion objects fit as factories: as counter { var value: get/semi set // generate getter and setter def step() { value = value + 1 } } in counter { def make() { | object | // object is a local variable object = new; // create object of associated class object.set(0); // use semi-protected setter object // return object } } class Counter = counter. Note that "new" without a class name as an argument, meaning "make an object of the associated class", can avoid the need for slightly different make methods for several classes that an idea is part of. (When used in instance methods, it means "make another object of this class".) This factory method was very wordy, so that lots of comments could be added. This is how you would normally write it: in counter { def make() (new).set(0) } You make a new Counter using "Counter.make()". Companion objects can be refered to by their fully qualified name, of the form "Counter@main", or (usually) without the name space component. In the latter case, variables in scope with the same name can have precedence; but the convention of giving classes names that start with a capital letter makes this a non-issue. 11 Conclusion In class based languages, class names are usually nouns. The system described above invites the programmer to write many ideas as adjectives, or concerns; from these, classes are constructed, the names of which will often again be nouns. The second step, composition, makes that we need a second mechanism next to scoping to determine which methods are exported by the class; in traditional class based languages these mechanisms can be one and the same, because there is a hierarchy among superclasses and subclasses. I hope that writing concerns as ideas can contribute to untangling code conceptually, albeit at the price of more complex scoping mechanics. A good development environment should help the programmer cope with the latter in a way that it cannot do for the former. Appendices A Dynamic Features Tingle is not only an idea oriented language, it is also a dynamic language. It allows us to manipulate classes at run time, for instance using the operators "renew" and "asnew". The former changes the class of a single object ("become"). We can do this when someone graduates and proceeds to take a job at his university: class Student = person student. class Personnel = person personnel. renew john Personnel This leaves all variables of the former Student object intact, but the variables from the idea student and from the intersection of the ideas person and student will not be reachable anymore (because no more methods will be defined on the object that have these in scope) until John decides that it was more fun being a student, and changes again. This, by the way, is why Tingle has no constructors. Section 10 showed how to use companion objects as factories, but when you allow objects to be created piece by piece, neither constructors nor factories are satisfactory solutions. So instead of constructors, Tingle provides initializers: each individual instance variable can have any expression as its initial value. Such an expression is evaluated the first time when the variable is read (except if it is written to first). This involves a big trade-off though, as these expressions cannot receive parameters like constructors can; so companion objects as factories have to supplement them. Actually, if John takes a job at the university immediately after graduating, chances are that he will additionally enroll as a Ph.D. student. So we might compose a class out of the idea person, plus the idea student, plus the idea personnel. But this means that we might have to solve conflicts between top methods of Student and Personnel, where we would prefer to keep both, for instance getID() for student ID as well as for personnel ID. We can avoid having to fix this if we know that John will in all circumstances act either as a student, or as a member of personnel, but never both at the same time. In that case we can use "asnew" to create an extra role: johnAsPersonnel = asnew john Personnel This way john and johnAsPersonnel will always have the same internal state -- although only the variables of the idea person are really shared: all others are in scope for methods of only one of the roles -- but belong to different classes. (The objects john and johnAsPersonnel are identical for the "==" and "!=" operators.) It is interesting to consider what would happen if a method were to renew self, because the answer follows directly from the principles of idea orientation. As long as the new class after renew provides a set of top methods compatible with the old class (i.e. the same external API for the object), nothing dramatic will happen. The method itself will continue, will be able to access its variables and call other methods in scope, even if they belong to ideas that are not present in the new class, and the same goes for earlier methods waiting on the call stack to continue later. The object will only behave according to its new class when it receives a new message, either from itself or from another object; and switch back to its old behaviour when returning to methods that were already active before the renew. The program logic might get messed up, but at no point is there any danger of a hard crash. This is because classes may be fiddled with at run time, but ideas are immutable. Now that we know exactly what happens when one object changes class, we can continue to dynamically activated layers, which will change the class of many objects at once. B Ideas as Layers Suppose we are making a computer game in which many things work slightly different when it is night in the game. We can handle this by sprinkling the code with "if night then ... else ...". Alternatively, can make an idea atNight and concentrate all the differences there: as atNight { as drunk() { // adapt behaviour methods to get into more fights } as hyena() { // adapt to howl more } as ... } Now drunks and drunks at night are the same individuals, so it is not convenient to use the new idea atNight through extra classes: class Drunk = drunk. class DrunkAtNight = drunk atNight. Using such pairs of classes, we could of course do a "renew" for all the drunks in the game when night falls and another one when day breaks, and the same for hyenas and all other affected classes, but that would be tedious. So instead of using atNight as a component for classes, we activate it as a layer. To do some next step in the game under night-time conditions, we do: with atNight { next_step() } For the duration of next_step(), all classes in the system will be temporarily composed with the idea atNight. For many classes, this will of course have no effect at all. But the behaviour of drunks and hyenas etc. will change. (In principle, and as opposed to the effect of "renew" and "asnew", this change only happens for the current thread, but as Tingle currently does not provide threads at all, that is hard to demonstrate.) Activating layers is a mechanism used in context oriented programming. When applied in idea oriented programming, it changes the meaning of message sends only: it has no influence at all on calls through scope, or on variable accesses. So the next_step() in the example is guaranteed to be the same next_step() that would be called at the same place if its call were not wrapped in a layer activation. If that is not the intended effect, we will need self.next_step(). Dirk van Deun, 2007-2015 (dirk at dinf.vub.ac.be)