This is loops.info, produced by makeinfo version 4.0 from /usr/local/src/BUILD/logo-mode/loops-guide.texi.  File: loops.info, Node: ADDITIONS TO LOGO LIBRARY, Next: OBJECT ORIENTED PROGRAMMING, Prev: ADDITIONS TO LOGO, Up: Top Additions to Logo Library ************************* There is no difference between these procedures and other procedures in Logo library. They are coded in standard Logo, and are automatically copied to logolib directory during installation of LOGO-MODE on UN*X machines. On other OSs, you will have to do this manually. They are loaded ON DEMAND, just like the rest of Logo library. Most of them are included, because they are needed by LOOPS code, or were useful during development testing and profiling LOOPS (like `time' procedure). Some will not be portable like (`time' again), but all are useful in their own right. * Menu: * BEFORE:: * MAX:: * MIN:: * EVENP:: * ODDP:: * ZEROP:: * IDENTITY:: * LASTdPAIR:: LAST.PAIR * SPLITdWORD:: SPLIT.WORD * TIME:: * BEGIN::  File: loops.info, Node: BEFORE, Next: MAX, Prev: ADDITIONS TO LOGO LIBRARY, Up: ADDITIONS TO LOGO LIBRARY before ------ (before word) before word1 word2 (before word1 word2 ...) operation. Outputs (alphabetically) smallest word from input words. Uses beforep for comparison.  File: loops.info, Node: MAX, Next: MIN, Prev: BEFORE, Up: ADDITIONS TO LOGO LIBRARY max --- (max number) max number1 number2 (max number1 number2 ...) operation. Outputs numerically largest number from input numbers. Uses greaterp internally.  File: loops.info, Node: MIN, Next: EVENP, Prev: MAX, Up: ADDITIONS TO LOGO LIBRARY min --- (min number) min number1 number2 (min number1 number2 ...) operation. Outputs numerically smallest number from input numbers. Uses lessp internally.  File: loops.info, Node: EVENP, Next: ODDP, Prev: MIN, Up: ADDITIONS TO LOGO LIBRARY evenp ----- evenp number even? number outputs TRUE if the input is an even number, FALSE otherwise.  File: loops.info, Node: ODDP, Next: ZEROP, Prev: EVENP, Up: ADDITIONS TO LOGO LIBRARY oddp ---- oddp number odd? number outputs TRUE if the input is an odd number, FALSE otherwise.  File: loops.info, Node: ZEROP, Next: IDENTITY, Prev: ODDP, Up: ADDITIONS TO LOGO LIBRARY zerop ----- zerop number zero? number outputs TRUE if the input is equal to 0, FALSE otherwise.  File: loops.info, Node: IDENTITY, Next: LASTdPAIR, Prev: ZEROP, Up: ADDITIONS TO LOGO LIBRARY identity -------- identity stuff outputs its argument. It is normally used as a first input to Higher order Logo Procedures: to list.copy :lst op map "identity :lst end  File: loops.info, Node: LASTdPAIR, Next: SPLITdWORD, Prev: IDENTITY, Up: ADDITIONS TO LOGO LIBRARY last.pair --------- last.pair list outputs last pair of a list. (as in last CONS CELL, or last node). It will work with words, but makes no sense there--using last is faster. Normally used for creation of circular structures: make "lst [1 2 3 4 5] show :lst => [1 2 3 4 5] .setbf last.pair :lst :lst show :lst => [1 2 3 4 5 1 2 3 4 5 1 2 3 ...]  File: loops.info, Node: SPLITdWORD, Next: TIME, Prev: LASTdPAIR, Up: ADDITIONS TO LOGO LIBRARY split.word ---------- (split.word word) split.word word delimiter outputs a list, consisting of parts of the input WORD split on the delimiter (with delimiter removed). When applied to one input only (a word) delimiter is space by default. show split.word "circular.list.class ". => [circular list class]  File: loops.info, Node: TIME, Next: BEGIN, Prev: SPLITdWORD, Up: ADDITIONS TO LOGO LIBRARY time ---- time instructionlist operation or command, depending on wether INSTRUCTIONLIST outputs or not. Runs the INSTRUCTIONLIST, possibly outputs the result of INSTRUCTIONLIST, and then prints the total time used. Resolution is poor--second only, so set up longer tests for accurate measurements. Works only on UN*X. time [quick.sort map "random iseq 1 1000] [0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 ...] Elapsed time: 15 seconds  File: loops.info, Node: BEGIN, Prev: TIME, Up: ADDITIONS TO LOGO LIBRARY begin ----- begin list.of.instructionlists operation. A control structure that runs all INSTRUCTIONLISTS, and outputs the result of the last one. Can be defined as: run apply "se :list.of.instructionlists Only the last INSTRUCTIONLIST can output, or `begin' will not reach it. It appears to be similar to `.and' control structure, but there are some important differences; `.and' will exit on first input that returns "FALSE--`begin' can not be used with inputs that return anything (except the last one). `.and' can not handle inputs with side effects--`begin' can. *Note dAND::.  File: loops.info, Node: OBJECT ORIENTED PROGRAMMING, Next: TUTORIAL, Prev: ADDITIONS TO LOGO LIBRARY, Up: Top Object Oriented Programming *************************** LOOPS is a rudimentary Logo Object Oriented Programming System. It is really too small, and slow, for serious practical use, but--with Logo being what it is, language for learning and exploration, LOOPS fits that context nicely. If you want to try developing your own set of classes in Logo, this is a good start. * Menu: * LOGO AND OOP:: * OOP AUXILIARY PROCEDURES:: * BASE CLASSES:: * TURTLES:: * IMPLEMENTATION::  File: loops.info, Node: LOGO AND OOP, Next: OOP AUXILIARY PROCEDURES, Prev: OBJECT ORIENTED PROGRAMMING, Up: OBJECT ORIENTED PROGRAMMING Introduction ============ The core ideas of object oriented programming differ considerably from the ideas that were used to develop Logo. At a minimum, we should be able to create objects as black boxes. That is--objects will have their private persistent storage, and their own methods (functions) to operate on that storage. Logo provides neither. It does have private storage (local variables), but they are not persistent. As soon as the object has completed its immediate task, its local storage is freed. On the next invocation of the same object, local storage is reinitialized, and thus its previous state forgotten. To clarify this, consider that we want to create a counter object. The counter's storage is initialized to 0 on creation of object. We want to be able to send the counter messages `increment', and `decrement'. We could try to do this in Logo by defining procedure `counter': to counter :message localmake "storage 0 if equalp :message "increment [make "storage :storage+1 op :storage] if equalp :message "decrement [make "storage :storage-1 op :storage] op (se "counter "does "not "understand :message) end No matter how many times we send counter object message `increment', it will always output 1, likewise if the message is `decrement', it will always output -1. This is obviously not what we want. We could use a global variable STORAGE, and initialize it only once. This would work, but it would also violate the principle of object as a black box. Its private storage has now moved to global name-space, and all other procedures can access it and tamper with it. To further aggravate the problem, if we wanted a second counter, we would have to create the second global storage (with a different name), and so on. This also means that second object can not use the same procedure as the first one, because the name of the storage has changed. Not being able to use the same procedure as a template for creating all objects of the same type, means that we cannot create counter class, and classes are the foundation of OOP. We still haven't mentioned the second problem--private methods. Logo does not have local procedures, therefore objects can not have them either. Fortunately, the solution for this problem is obvious. *Note LOCAL PROCEDURES::. We can use `letrec' to create object methods--but let's not get ahead of ourselves. We still need to solve the persistent (static) storage problem. Static storage obviously must be some sort of global storage. The problem is twofold; how to hide it to make it inaccessible to other objects, and how to handle different names, so that the same procedure (class) can be used as a template for creation of all of its instances (objects). I have chosen Logo property lists for static storage. Handling of different names is done automatically by the `object.maker', class to object transformer (compiler) procedure. Just how this is done will be explained later. *Note IMPLEMENTATION::. This is all completely transparent for the LOOPS user/programmer, and the full understanding of how `object.maker' works is not important. It is however important to understand the following principles: LOOPS objects are procedures. Some of them are real Logo procedures (top level objects, but most will be virtual procedures (just like procedures in `letrec'). Programmer programs them by writing a template procedure (class); `object.maker' is then applied to the class procedure (with possible initializing inputs), to actually create an object. After that object can be used (run) by sending it messages, to which it knows how to respond. Class (template) procedures have the following syntax: to classname.class [input ...] [compile-time code used to set up the environment for static line] ... [static [...]] ... lambda :message run-time code that will form object procedure ... end That would be: zero or more INPUTs to initialize the object's static storage zero or more lines of code that will run during compile--time zero or more lines starting with word `static' one `lambda :message' line one or more lines of Logo code that will actually form the object procedure Note that square brackets mean optional (zero or more), and are not part of the class syntax. The class procedure name *must* end with `.class'. `object.maker' does not care about that, and will accept class template of any name, but the extension must be there anyway, to protect class procedures from accidental compilation. If you use logo-mode command COMPILE WORKSPACE, this will first filter out all procedures that have `.class' extension, and then compile the rest. Compiling a class, firstly--makes no sense, and secondly--will break the `object.maker'. Lambda line is mandatory. It simply marks the divide between compile time part of the object (top), and runtime part (bottom). The variable `:message', does not have to be called that (it just makes sense to do so). You can call it anything, as long as you are consistent. The compile time part of the class (lines above the `lambda' line) will not show in object itself, at least not in that form. They serve the purpose of allocating and initializing the object's static storage, checking the validity of optional inputs, and whatever else is needed to set up the compile-time environment for `static' line. The runtime part of the class (lines below `lambda' line will form the body of object procedure as they are, or compiled by loops.compiler if they contain `case', `cond', or `letrec' structures. Compilation is automatic, started by `object.maker'. It cannot be run manually, as each object creates (zero or more) sub-objects from which it inherits some of its methods. As these objects are virtual procedures, loops.compiler (run manually from top-level) would not be able to compile them. Objects are defined this way: define "objectname (object.maker "classname.class [input ...]) This will create object (global procedure) OBJECTNAME, and initialize its static storage with INPUT. Input is optional. To use the object, send it messages: send "objectname "message (send "objectname "message1 "message2 ...) It would be easier to use a direct way to invoke objects: objectname "message but I chose to use the `send' procedure instead. There are two advantages to using `send'. It let's me pass the name of the object (to the object) consistently, in case object needs to call itself, and lets me dereference object's name several levels deep, therefore ensuring uniform syntax for calling objects; object name is ALWAYS quoted. Otherwise, in certain situations the programmer would have to dereference the name manually by using `:' instead of `"'. I am aware that this sounds a bit vague now, but it will be easier to understand when we discuss base classes. *Note BASE CLASSES::.  File: loops.info, Node: OOP AUXILIARY PROCEDURES, Next: BASE CLASSES, Prev: LOGO AND OOP, Up: OBJECT ORIENTED PROGRAMMING OOP Auxiliary Procedures ======================== Before we can start writing classes, we need a small set of auxiliary procedures, that make LOOPS work. * Menu: * OBJECTdMAKER:: OBJECT.MAKER * SEND:: * INDIRECT:: * DELEGATE:: * STATICdMAKE:: STATIC.MAKE * METHODdEXPORT:: METHOD.EXPORT  File: loops.info, Node: OBJECTdMAKER, Next: SEND, Prev: OOP AUXILIARY PROCEDURES, Up: OOP AUXILIARY PROCEDURES object.maker ------------ (object.maker classname optional.arguments) operation. Outputs the text (in the format that Logo procedure `text' outputs) of the newly created and compiled object. Takes one mandatory input--CLASSNAME, and zero or more optional arguments, normally initializing values for object's static storage. How many--if any, depends on the class itself, as it is the class that needs optional inputs.  File: loops.info, Node: SEND, Next: INDIRECT, Prev: OBJECTdMAKER, Up: OOP AUXILIARY PROCEDURES send ---- send objectname message (send objectname message1 message2 ...) operation. Sends object OBJECTNAME the message MESSAGE, and outputs the result returned by the object. The main task of `send' is to completely dereference the OBJECTNAME, and send to the object either the real global *name* of the object (if object is a global procedure), or the actual *object itself*, if object is a virtual procedure.  File: loops.info, Node: INDIRECT, Next: DELEGATE, Prev: SEND, Up: OOP AUXILIARY PROCEDURES indirect -------- indirect idirect.objectname message (indirect indirect.objectname message1 message2 ...) operation. Used for indirectly addressing objects, instead of: send send indirect.objectname "indirect message Outputs whatever indirectly addressed object outputs, as a response to the MESSAGE.*Note DIGITAL CLOCK::.  File: loops.info, Node: DELEGATE, Next: STATICdMAKE, Prev: INDIRECT, Up: OOP AUXILIARY PROCEDURES delegate -------- delegate objectname messagelist operation. Takes two inputs, OBJECTNAME, and MESSAGELIST, and invokes the object on the `messagelist'. As the name suggest, it delegates (passes on part of the objects job, to the classes (objects) whose storage and methods `objectname' inherits.  File: loops.info, Node: STATICdMAKE, Next: METHODdEXPORT, Prev: DELEGATE, Up: OOP AUXILIARY PROCEDURES static.make ----------- static.make varname val command. Works like Logo `make'. Sets the value of VARNAME to the value VAL, but also changes the value of the property VARNAME to the value VAL. To fully understand the functioning of static storage, and bucket.class, one must understand the implementation of `static.make': .macro static.make :name :value pprop :#in.procedure# :name :value output (list "apply ""make (list :name :value)) end This code should be examined after reading `bucket.class' implementation *Note BUCKET::. This procedure is actually redundant. It was the part of the first implementation of LOOPS, and is left here (just as the bucket.class is), because it is easy to implement, and understand--for beginner LOOPS programmers to play with. The current implementation of LOOPS uses pointers instead of `ctatic.make'.  File: loops.info, Node: METHODdEXPORT, Prev: STATICdMAKE, Up: OOP AUXILIARY PROCEDURES method.export ------------- method.export classlist methodtext operation, with side-effects. Takes two inputs, a list of classes, to which method is to be exported, and the METHODTEXT (list of lists). Outputs the compiled version of the METHODTEXT.  File: loops.info, Node: BASE CLASSES, Next: TURTLES, Prev: OOP AUXILIARY PROCEDURES, Up: OBJECT ORIENTED PROGRAMMING Base Classes ============ This is a minimum set of classes that were needed to test the system. Some were chosen more on the merit of being a challenge to develop, than of real usefulness to everyday programming. All objects created (defined) by these classes are black boxes that receive messages and act upon them. Just what messages are understood by each object will be listed later. There is one message common to all objects, and that is TYPE. All objects will respond to that message by returning their *class* type. If you have defined the object `counter1', based on REWINDING.COUNTER.CLASS, the `counter1''s response to message `send "counter1 "type' will be REWINDING.COUNTER.OBJECT, and not COUNTER1.OBJECT. The other important fact to remember about a class is; although all classes are valid Logo procedures, they are NOT meant to be executed directly. Their purpose is to serve only as a blueprint (template), based on which `object.maker' creates an instance of a class--object that can be executed directly. To prevent possible confusion as a result of user trying to run the class procedure directly, `lambda' is defined as a procedure that throws TOP-LEVEL with an error message. * Menu: * BASE OBJECT:: * BUCKET:: * PAIR:: * SWAPdPAIR:: SWAP.PAIR * BORDER:: * COUNTER:: * REWINDING COUNTER:: * CONTAINER:: * STACK:: * CIRCULAR:: * DOUBLEdEND:: DOUBLE.END * QUEUE::  File: loops.info, Node: BASE OBJECT, Next: BUCKET, Prev: BASE CLASSES, Up: BASE CLASSES base object ----------- `base.object' is the one exception here. Technically it is not a class at all, but a straight forward object (globally defined procedure). There are two good reasons for defining `base.object' this way. Every other object (class) needs the `base.object' as the last object in the delegation (inheritance) chain, which means, that the number of `base.object's defined in the end would be large. That's OK if there was a real need for so many--or even two separate `base.objects', but there is not. `base.object' is the only object (class) that I have defined in LOOPS that does not have its own static storage. Simply put--there is no need for more than one. However note; the `base.object' *must* be included in *all* classes that you define either explicitly; as in `delegate "base.object :message', or implicitly by delegating to another class that (somewhere down the chain of delegation) has explicit delegation to `base.object'. The reasons for this rule will became obvious, after the explanation of messages that `base.object' responds to. `error' This is user generated error (user requested error) in some other class that was delegated to the `base.object', which then displays the message that was sent with it, and throws top-level. That is necessary, because Logo does not have Scheme like ERROR procedure--so instead of printing error message and throwing top-level everywhere you need this, you simply send the word "error with the error message text to the `base.object'. `erase.object' `base.object' is the only object that possesses the method for deleting any top-level object, its sub-objects, and all of their static storage. *This is very important*. Logo automatic memory management does not work with LOOPS. Garbage collector will not know which virtual objects (values in property lists) to trash if you delete manually (with `erase') only the top-level object. As objects take up a lot of space (far more than standard Logo procedures), it is important to send the top-level object message ERASE.OBJECT, when it is no longer needed. `anything-not-caught-by-higher-level-objects' This is a catch-all error message. Any message (probably misspelled) sent to any of the objects higher in the delegation chain will filter down to the `base.object', which will then display the message: MESSAGE NOT UNDERSTOOD BY OBJECT: OBJECTNAME, and throw top-level.  File: loops.info, Node: BUCKET, Next: PAIR, Prev: BASE OBJECT, Up: BASE CLASSES bucket class ------------ This class was designed as the only access to any object's static storage. However, now is redundant, and is included here only because it is simple to understand, implement, and explain. It is still available in LOOPS, so anybody wishing to use it, can do so. to bucket.class :init static [[contents :init]] lambda :msg local "message make "message first bf :msg if.tf equalp :message "get [op :contents] if.tf equalp :message "indirect [op :contents] if.tf equalp :message "set ~ [static.make "contents last :msg op :contents] if.tf equalp :message "type [op "bucket.object] op delegate "base.object :msg end Probably the best way to understand this `static storage' business, is to create one instance of the BUCKET.CLASS, and inspect its code: define "bucket1 (object.maker "bucket.class 0) show fulltext "bucket1 to bucket1 :msg local [#in.procedure# contents] make "contents gprop "g1.g2.bucket.class "contents make "#in.procedure# "g1.g2.bucket.class local "message make "message first bf :msg if.tf equalp :message "get [op :contents] if.tf equalp :message "indirect [op :contents] if.tf equalp :message "set ~ [static.make "contents last :msg op :contents] if.tf equalp :message "type [op "bucket.object] op delegate "base.object :msg end The output of `fulltext' was hand-formated to make it easier to read. Let's first deal with the part of the `bucket.class' below LAMBDA line. If you compare class code and object code, you will see that they are identical. `object.maker' simply copied all lines below LAMBDA to the object. That is why this part is called the run-time part of the class. I take it--no further explanation is necessary here. The top part consisting only of the line `static [[contents :init]]' is more interesting, and is responsible for the top three lines in the object. This part (above LAMBDA line) is called; the compile-time part of the class. The format of `static' line is following: static [[static-variablename1 value-expression1] [static-variablename2 value-expression2] ... ] static [[static-variablename3 value-expression3] ...] Anybody familiar with Lisp will realize that this is just a copy of the Lisp LET form. The second STATIC line is equivalent to nested LET lines. Why do we need more than one STATIC line? To resolve the scope problem. All VARIABLENAME--VALUE-EXPRESSION pairs in one STATIC line are executed in parallel, which means that VALUE-EXPRESSION2 can not use the value of STATIC-VARIABLENAME1, because--technically speaking, at the moment when VALUE-EXPRESSION2 needs this value, it does not yet exist. However, the second STATIC line can use values of both STATIC-VARIABLENAME1, and STATIC-VARIABLENAME2. When `object.maker' finds the word STATIC in the text of a class it will convert the list after it to the top three lines in the `bucket' object, and prepend it to the runtime part of the class. The important issue to understand here is that although `object.maker' is an operation (outputs the code of the object), it is an operation with side-effects. Before it outputs the text, it will carry out the following assignment (command): pprop "g1.g2.bucket.class "contents 0 This 0, is the initializing value passed to the `object.maker' as a second input. It is, perhaps, easier now to understand the line `make "contents gprop "g1.g2.bucket.class "contents' in the `bucket1' object. During compile-time, static storage--property list with the name "g1.g2.bucket.class was created. Its property "contents was set to 0. During run-time, when the object `bucket1' is executed, first thing it must do, is to retrieve this property, and make it conveniently available as a local variable with the same name; CONTENTS. After that `bucket1' can use it as if it were an ordinary local variable. There is one important exception. If `bucket1' needs to (permanently) change the value of CONTENTS it must use `static.make'. Logo `make' would only change the value of the local variable CONTENTS, which is not enough if we want the change to persist until the next invocation of `bucket1'. That's why we need the second variable with the name #IN.PROCEDURE#. This variable is created for every BUCKET.CLASS object, and is used by `static.make'. *Note STATICdMAKE::. It should be obvious that the name #IN.PROCEDURE# is best avoided in any of the classes you define, as this could result in the name clash. Bucket class responds to the following messages: `get ; indirect' Outputs the value of the variable CONTENTS (in our case 0). `set VALUE' Changes the value of the (static) variable CONTENTS to VALUE, and outputs that value. After having read and understood all of this--you can safely forget the `bucket.class', and concentrate on the next one (`pair.class'), as this is the only static storage access class used in the rest of the LOOPS code.  File: loops.info, Node: PAIR, Next: SWAPdPAIR, Prev: BUCKET, Up: BASE CLASSES pair class ---------- As mentioned before, this should be the *only* class used to acces static storage. Its implementation is superior to bucket.class (which it replaces) for two reasons; It does not have to use hand written kludge (like procedure `static.make') to change static storage. Insted, it uses Logo primitives `.setfirst', and `.setbf'. This makes the code faster and cleaner. The added benefit is that the `pair.class' can store two independent values instead of only one. Unlike `bucket.class', the `pair.class' accesses static storage via the header--a cons cell, commonly called `dotted pair' or `improper list'. The storage is changed by directly manipulating pointers to dotted pair's first and butfirst parts. If this `dotted pair' has caused confusion--well, it is a standard Lisp structure that apparently exists in Logo as well (although it is not documented). It consists of one cons cell (node) that holds pointers to first and butfirst parts. The only difference between dotted pair, and ordinary list is that butfirst pointer of the proper list must point either to the rest of the (proper) list (if the list has more than one element), or to the empty list. The butfirst pointer of a dotted pair can point to an atomic structure (no empty list at the end). The `pair.class' header cell, does not always have to be dotted pair, it can also be a proper list. If the the class instance (object) was created with only one initializing value, then it will be a proper list. You will understand this better after examining the class code: to pair.class :xinit [:yinit []] local "cons.cell make "cons.cell (list :xinit) .setbf :cons.cell :yinit static [[pair.o :cons.cell]] lambda :msg op case [ [first bf :msg] [[get getx indirect] [first :pair.o]] [[set setx] [.setfirst :pair.o last :msg first :pair.o]] [[gety] [bf :pair.o]] [[sety] [.setbf :pair.o last :msg bf :pair.o]] [[raw] [:pair.o]] [[p2l] [list first :pair.o bf :pair.o]] [[type] ["dotted.pair.object]] [else [delegate "base.object :msg]]] end First we have to explain the purpose of the first two lines. These lines belong to the compile-time part of the class, but they differ substantially from the STATIC line. Static line will run during compile-time of the class, but will also leave the result in a run-time part of the object. Lines before `static' line will run during compile-time, but will not output to the run-time part of the object. They are here simply to set up the environment for the STATIC line. That's why all of them *must* come before the first STATIC line. In the case of `pair.class', first two lines will create the dotted pair structure. It is easier to understand now, that--if YINIT is not given; it will default to the empty list, thus making the CONS.CELL into a proper list. When it comes to the number of inputs an LOOPS class can handle; they are just like ordinary Logo procedures. They take required inputs (:XINIT), optional inputs ([:YINIT []], and rest inputs (pair.class does not have those). The input syntax is the same as the standard Logo syntax, with the exception that the default number of inputs is not needed. It is not an error to append a number specifying that, but do not waste your time--`object.maker' will simply strip it from the class text. Class is never applied to its inputs, instead--`object.maker' is applied to both, the class and its inputs. As the call *must* be enclosed in parenthesis, there is never any ambiguity, about their number. The run-time part of the `pair.class' does not use `static.make'. Instead, to set the first half, `car' of the CONS.CELL it uses `.setfirst', and to set the second half, `cdr', it uses `.setbf'. One point is worth noting again. Only the variables that were given value in the STATIC line will be visible in the run-time part of the class instance; e.g., PAIR.O. The CONS.CELL variable, on the other hand will not. The PAIR.CLASS responds to following messages: `get ; getx ; indirect' Both GET and GETX will output the first part of the `pair.o'. It is natural to use GET, if pair object contains only one value (the other is an empty list). If we are using pair object to hold two distinct values (like X, and Y coordinates in the plane), then use GETX, to avoid the confusion. `set VALUE; setx VALUE' Both will set (change) the value of the first part of `pair.o' to be the VALUE. `gety' Will output the value of the second part of the `pair.o'. Meaningful only if you use object to hold two values. `sety VALUE' will set (change) the value of the second part of `pair.o'. `raw' Will output the raw value of the `pair.o'. Often the result will not be what one expects; e.g.; if you created the object with: define "pair1 (object.maker "pair.class "x "y) The result printed by Logo will be: show send "pair1 "raw [x ] This is just the way Logo print-er sees the dotted pair structure, it does not mean that the Y part has vanished. You can check this with the next message. `p2l' This will output first and second parts of `pair.o', formated as a list of two objects. (P2L stands for; pair to list) show send "pair1 "p2l [x y] The main difference between RAW, and P2L is that RAW hands you the pointer to the actual object itself, while P2L only outputs the coppy of the object. From the standpoint of OOP RAW would probably be considered illegal, because it opens the backdoor to the private storage.  File: loops.info, Node: SWAPdPAIR, Next: BORDER, Prev: PAIR, Up: BASE CLASSES swap.pair --------- `swap.pair.class' is based on the PAIR.CLASS, and adds only one new message to it--SWAP. It can be used instead of the PAIR.CLASS if you need to swap two parts of static storage. to swap.pair.class :xinit [:yinit []] static [[pair.o [(object.maker "pair.class :xinit :yinit)]]] lambda :msg local "tmp op case [ [first bf :msg] [[swap] [make "tmp send "pair.o "gety] [ignore (send "pair.o "sety send "pair.o "get)] [(send "pair.o "set :tmp)]] [[type] ["swap.pair.object]] [else [delegate "pair.o :msg]]] end `swap' Swaps the first and butfirst parts of the PAIR object, and returns the first part.  File: loops.info, Node: BORDER, Next: COUNTER, Prev: SWAPdPAIR, Up: BASE CLASSES border class ------------ `border.class', although listed here as a simple base class, is very different, and the explanation of WHAT, and HOW it does (whatever it is that it does), would be only confusing here. We shall get back to it in the *tutorial*, when explaining the QUEENS8 class. Very broadly; it serves as a stop object. Let's say we have several objects of the same kind, calling each other to perform some task. The task is supposedly finished when the last object in the line has to call the next object. An instance of the `border.class' can be used as one such NEXT object. It will stop the execution, (possibly) display the message it was initialized with, and (possibly) call some previous objects to collect results of computation from them. It is very difficult, to determine even what messages does the `border.object' respond to, just by looking at its code: to border.class :text [:objects []] [:msgs []] static [[text.o [(object.maker "pair.class :text)]] [objmsg.pairs [(object.maker "pair.class :objects :msgs)]]] lambda :message local [self msg] make "self first :message make "msg bf :message case [ [first :msg] [[exit.outer] [op "false]] [[erase.object] [op delegate "base.object :msg]] [else [ ;; output text and all results of terminal object msg calls ;; use 'self to send msg 'exit.outer to border.class object -- as a ;; flag to break out of the outer loop (see queens8.class) op se send "text.o "get ~ (map [[x y] [op apply "send se :x :y]] send "objmsg.pairs "get send "objmsg.pairs "gety)]]] end `exit.outer' This is the only private method. It will output the word "false, which can be used to break the execution of an outer loop (as in `while'). `erase.object' The only other message `border.object' will catch and delegate to the `base.object'. There are many other messages that border object can receive, but for their dispatching, it will use a sort of multiple inheritance--calling different objects, with different messages. This is probably more than enough about `border.class' for now.  File: loops.info, Node: COUNTER, Next: REWINDING COUNTER, Prev: BORDER, Up: BASE CLASSES counter class ------------- This is the first class that we used so far (apart from BORDER.CLASS, which was not explained), that not only sets up its private storage (INIT.VAL), but also sets up one private (virtual) object to handle the storage getting and setting tasks. The novelty is also the IF NOT ... line, before the STATIC line. That line works as in any ordinary Logo procedure--stops the execution of a procedure if the variable :INIT does not contain a number. The point to understand is; which procedure is stopped? The object created by the `counter.class', or the `object.maker'? Answer is very simple--this line belongs to the compile-time part of the class, therefore it must be the `object.maker' that is stopped. The IF NOT ... line will not appear in the object at all. to counter.class :init if not numberp :init [(send "base.object "error [Input must be a number!])] static [[init.val :init] [pair.o [(object.maker "pair.class :init)]]] lambda :msg op case [ [first bf :msg] [[inc] [(send "pair.o "set sum (send "pair.o "get) 1)]] [[dec] [(send "pair.o "set (send "pair.o "get) - 1)]] [[reset] [(send "pair.o "set :init.val)]] [[type] ["counter.object]] [else [delegate "pair.o :msg]]] end If we define an instance with: define "c1 (object.maker "counter.class 0) and check the code with: show fulltext "c1 to c1 :msg local [init.val pair.o] make "init.val gprop "g57.g58.counter.class "init.val make "pair.o gprop "g57.g58.counter.class "pair.o op run [ local "#target make "#target run [first bf :msg] ifelse.tf memberp :#target [inc] [(send "pair.o "set sum (send "pair.o "get) 1)] [ifelse.tf memberp :#target [dec] [(send "pair.o "set (send "pair.o "get) - 1)] [ifelse.tf memberp :#target [reset] [(send "pair.o "set :init.val)] [ifelse.tf memberp :#target [type] ["counter.object] [delegate "pair.o :msg]]]]] end You have probably notice that the `case' has disappeared; `c1' code has been compiled. Secondly, there are two static variables created; one is the initializing input. We can check its contents with: show gprop "g57.g58.counter.class "init.val 0 This, as expected, prints 0. The next one is more interesting: show gprop "g57.g58.counter.class "pair.o [[msg] [local [pair.o]] [make "pair.o gprop "g57.g59.pair.class "pair.o] [op run [local "#target make "#target run [first bf :msg] ifelse.tf memberp :#target [get getx] [first :pair.o] [ifelse.tf memberp :#target [set setx] [.setfirst :pair.o last :msg first :pair.o] [ifelse.tf memberp :#target [gety] [bf :pair.o] [ifelse.tf memberp :#target [sety] [.setbf :pair.o last :msg bf :pair.o] [ifelse.tf memberp :#target [raw] [:pair.o] [ifelse.tf memberp :#target [p2l] [list first :pair.o bf :pair.o] [ifelse.tf memberp :#target [type] ["dotted.pair.object] [delegate "base.object :msg]]]]]]]]]] I have formatted this output, as it would be very difficult to read it as one line only. If you compare this to the code of the `pair.class', you will see that they are the same (except for the `case' form being compiled into standard Logo code). What this means is--object `c1' has created its private instance of the `pair.class', instead of just its private static storage. The rational is very simple. `c1' does not care how is the value of the counter actually accessed or changed in memory. This is delegated (inherited) from the pair.class. Therefore object `c1' can only read or alter its contents, by asking (sending messages) to the `pair.o' to do so. If you want to see what is the current value of the counter, you have to check the `pair.o' static storage, not the `c1' storage. Let's perform this test: pr send "c1 "inc 1 pr send "c1 "inc 2 Now we will check the `pair.o'bject's static storage: show gprop "g57.g59.pair.class "pair.o [2] This should be enough to convince you. Note that it is only the `c1' object that exists as a globally defined procedure. `pair.o' is just a list value (that Logo knows how to execute) stored in g57.g58.counter.class property list. `counter.class' responds to following messages: `inc' Increments the counter, and outputs its incremented value. `dec' Decrements the counter, and outputs its decremented value. `reset' Resets the value of the counter to be the INIT.VAL, and outputs that value. The rest of the messages are delegated to the `pair.o', which means that the counter can also respond to messages GET, SET ... Actually to all messages that an instance of a PAIR.CLASS knows how to respond to. This is a brief example of interaction with the `c1' object: pr send "c1 "reset 0 pr send "c1 "inc 1 pr send "c1 "inc 2 pr send "c1 "dec 1 pr send "c1 "get 1 pr send "c1 "getx 1 show send "c1 "gety [] show send "c1 "reset 0 show send "c1 "type counter.object show send "c1 "increment ERROR: increment not understood by object: c1 show send "c1 "erase.object [18599 191950] The last one; ERASE.OBJECT has erased the `c1' object, and all of its static storage (including the `pair.o' and its static storage) from the memory. The output is showing the garbage collector's statistics. Numbers indicate the number of nodes in use now, and before call to Logo `GC'.  File: loops.info, Node: REWINDING COUNTER, Next: CONTAINER, Prev: COUNTER, Up: BASE CLASSES rewinding counter class ----------------------- REWINDING.COUNTER.CLASS is a simple modification of a `counter.class', from which it inherits most of its methods. It is based on the model of the old analog mileage counter in cars. It starts with some :INIT value, increments the counter by 1 to :LIMIT value, and then restarts with :INIT value again. to rewinding.counter.class :init :limit if not numberp :limit [(send "base.object "error [Input must be a number!])] static [[init.val :init] [rewind :limit] [counter.o [(object.maker "counter.class :init)]]] lambda :msg case [ [first bf :msg] [[inc] [op ifelse (send "counter.o "get) < :rewind [(send "counter.o "inc)] [(send "counter.o "reset)]]] [[type] [op "rewinding.counter.object]] [[dec] [op delegate "base.object :msg]] [else [op delegate "counter.o :msg]]] end During compilation of the REWINDING.COUNTER.CLASS, the `object.maker' builds the global object of REWINDING COUNTER TYPE, which builds its own private virtual object of the COUNTER TYPE, which builds its own private object of the PAIR TYPE, which will ultimately hold the value of the counter. At this stage you can begin to appreciate the `black box' metaphor for the OOPS. Can you tell, by examining the code of `rewinding.counter.class', whether its instances care which static memory access class is used; `bucket.class', or `pair.class'? The answer is no--they do not. And the matter of fact is that most of the classes in this chapter were developed (based on `bucket.class'), before the `pair.class' was implemented. To have the whole system use the `pair.class' instead of `bucket.class', I only had to alter those classes that directly use either bucket or pair class (the change was only in the different name used for memory access class). Of the classes you have seen so far, that would be only the `counter.class'. REWINDING.COUNTER, does not require any changes. (1) Here are some examples of interaction with one REWINDING.COUNTER object: define "rc1 (object.maker "rewinding.counter.class "x 3) ERROR: Input must be a number! define "rc1 (object.maker "rewinding.counter.class 0 "x) ERROR: Input must be a number! define "rc1 (object.maker "rewinding.counter.class 0 3) pr send "rc1 "get 0 pr send "rc1 "inc 1 pr send "rc1 "inc 2 pr send "rc1 "inc 3 pr send "rc1 "inc 0 pr send "rc1 "inc 1 pr send "rc1 "dec ERROR: dec not understood by object: rc1 pr send "rc1 "erase.object 29882 255950 ---------- Footnotes ---------- (1) All of this is true for the classes developed to use the BUCKET.CLASS. However classes that were written to exploit the dual storage capacity of PAIR.CLASS, can not switch back to the BUCKET.CLASS. These are BORDER.CLASS, DOUBLE.END.CLASSCONTAINER.CLASS, STACK.CLASS ...  File: loops.info, Node: CONTAINER, Next: STACK, Prev: REWINDING COUNTER, Up: BASE CLASSES container class --------------- The `container.class' is a simple class that works on lists only. Most of the work is done directly by the underlying `pair.class'. The `container.class' itself only adds some common list operations. to container.class static [[list.o [(object.maker "pair.class [])]]] lambda :msg op case [ [first bf :msg] [[first indirect] [first send "list.o "get]] [[fput] [(send "list.o "set fput first bf bf :msg send "list.o "get)]] [[bf butfirst] [ignore (send "list.o "sety first send "list.o "get)] [(send "list.o "set bf send "list.o "get)]] [[last] [last send "list.o "get]] [[bl butlast] [ignore (send "list.o "sety last send "list.o "get)] [(send "list.o "set bl send "list.o "get)]] [[lput] [(send "list.o "set lput last :msg send "list.o "get)]] [[empty? emptyp] [emptyp send "list.o "get]] [[count] [count (send "list.o "get)]] [[member? memberp] [memberp last :msg send "list.o "get]] [[member] [member last :msg send "list.o "get]] [[flush] [(send "list.o "set [])]] [[type] ["container.object]] [else [delegate "list.o :msg]]] end `container.class' responds to following messages: `first ; indirect' outputs the first element of the list (list.o) `fput VALUE' inserts the VALUE to the front of the list, and outputs the new list `bf ; butfirst' removes the first element from the list, and outputs the new list. First element is saved in the butfirst part of the pair, and can be retrieved with GETY `last' outputs the last element of the list `bl ; butlast' removes the last element from the list, and outputs the new list. Last element is saved in the butfirst part of the pair, and can be retrieved with GETY `lput VALUE' inserts the VALUE to the back of the list, and outputs the new list `empty? ; emptyp' outputs true if the list is empty `count' outputs the number of top-level elements in the list `member? ; memberp VALUE' outputs TRUE if the VALUE is a member of the list `member VALUE' outputs the part of the list starting with VALUE. If VALUE is not a `meberp' of the list, outputs empty list. `flush' empties the list, and outputs it ([]) Here are some examples of interaction with one CONTAINER object: define "cont1 (object.maker "container.class) show send "cont1 "get [] show (send "cont1 "fput 3) [3] show (send "cont1 "fput 2) [2 3] show (send "cont1 "fput 1) [1 2 3] show (send "cont1 "lput "a) [1 2 3 a] show (send "cont1 "lput "b) [1 2 3 a b] show send "cont1 "bf [2 3 a b] show send "cont1 "gety 1 show send "cont1 "bl [2 3 a] show send "cont1 "gety b show send "cont1 "count 3 show (send "cont1 "member 3) [3 a] show send "cont1 "get [2 3 a] show send "cont1 "flush [] show send "cont1 "empty? true show (send "cont1 "set [1 2 3 4 5]) [1 2 3 4 5] show send "cont1 "size ERROR: size not understood by object: cont1  File: loops.info, Node: STACK, Next: CIRCULAR, Prev: CONTAINER, Up: BASE CLASSES stack class ----------- Stack is a *first in last out* structure, based on `container.class'. Practically all of the work is carried out by an instance of CONTAINER.CLASS--STACK.CLASS only renames some of the list operations, and adds checks for empty list. to stack.class static [[holder.o [(object.maker "container.class)]]] lambda :msg op case [ [first bf :msg] [[push] [(send "holder.o "fput first bf bf :msg)]] [[pop] [ifelse.tf send "holder.o "empty? [(send "holder.o "error [Stack is empty])] [ignore send "holder.o "bf send "holder.o "gety]]] [[top] [ifelse.tf send "holder.o "empty? [send "holder.o "error [Stack is empty]] [send "holder.o "first]]] [[size] [send "holder.o "count]] [[type] ["stack.object]] [else [delegate "holder.o :msg]]] end `stack.class' responds to following messages: `push VALUE' pushes the VALUE onto the stack. VALUE becomes the first element of the underlying list structure. `pop' removes the first item from the stack (list), and outputs that value. Displays error message if the stack is empty. `top' outputs the first element of the stack, but does not remove it. It is an error if the stack is empty. `size' outputs the size of the stack (count of the list) Here are some examples of interaction with one STACK object: define "stack1 (object.maker "stack.class) show (send "stack1 "push "a) [a] show (send "stack1 "push "b) [b a] show (send "stack1 "push "c) [c b a] show send "stack1 "pop c show send "stack1 "size 2 show send "stack1 "get [b a] show send "stack1 "pop b show send "stack1 "top a show send "stack1 "empty? false show send "stack1 "pop a show send "stack1 "pop ERROR: Stack is empty show send "stack1 "erase.object [31313 207950]