This is loops.info, produced by makeinfo version 4.0 from /usr/local/src/BUILD/logo-mode/loops-guide.texi.  File: loops.info, Node: Top, Next: INTRODUCTION, Prev: (dir), Up: (dir) This manual documents the LOOPS, Berkeley Logo Object Oriented Programming System, written entirely in Logo, which runs as a subsystem under Berkeley Logo. The manual is divided into the following chapters: * Menu: * INTRODUCTION:: * CHANGES TO STANDARD LOGO PROCEDURES:: * ADDITIONS TO LOGO:: * ADDITIONS TO LOGO LIBRARY:: * OBJECT ORIENTED PROGRAMMING:: * TUTORIAL:: * ANSWERS:: * LOOPS CODE:: * INDEX::  File: loops.info, Node: INTRODUCTION, Next: CHANGES TO STANDARD LOGO PROCEDURES, Prev: Top, Up: Top Introduction ************ * Menu: * OVERVIEW:: * ENTERING AND LEAVING LOOPS::  File: loops.info, Node: OVERVIEW, Next: ENTERING AND LEAVING LOOPS, Prev: INTRODUCTION, Up: INTRODUCTION Overview ======== Copyright (C) 2000 by Hrvoje Blazevic This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. This program is still being written. It started as one of those, SEE IF I CAN DO IT projects, and although it has grown considerably since then, it is still just a `very' rudimentary OOPS. The basic ideas used in development came from Scheme_and_the_Art_of_Programming, therefore its attempt to add Lisp features to Logo should not come as a surprise. This manual assumes that you already know how to program in Logo, and merely describes the additional features, and their use. Implementation itself is not discussed in detail, but as the code is available for inspection, it should be easy to modify and improve upon. This EASY is meant as EASY, after heaving read *Computer_Science_Logo_Style, Volume_1_2_3 by Brian Harvey* (MIT Press, 1997). LOOPS code consists of four different parts, different in the way they interact with Logo, and in the way they are loaded. First two are the changes to some standard Logo procedures, and additions to Logo, including Lisp structures COND, CASE and LETREC, with the compiler for these structures. Third part is the actual LOOPS code. All three are in the .loops file, normally residing in your home directory, that must be loaded manually, or through Emacs logo-mode. The fourth part are the additions to Logo library, which are loaded in the standard way, on demand, just like the rest of ucblogo library. Without some of these additions, LOOPS will not work, but all are useful in their own right. When .loops is loaded, behavior of Logo changes, and although it will still run standard Logo programs, it also allows programs to be written in a new style. Here is a brief list of changes: Any value is TRUE except FALSE; (affecting IF AND OR NOT TEST WHILE ...) Serial AND OR .AND Local procedure definition; (LETREC Scheme special form, METHOD.EXPORT) Lisp control special forms COND CASE Compiler for special forms; (LOOPS.COMPILER) Object compiler; (OBJECT.MAKER + set of auxiliary procedures) Set of base classes for OOP. For those that have experience with any REAL OOPS, do not expect to find Smalltalk-80 here. When designing LOOPS, I was primarily concerned with HOW to implement it then WHAT can it practically do. On top of that I have very limited experience with OOP. Unfortunately, all this is expensive. Programs will run slower than with standard Logo. Another detracting side effect is that normally very informative Logo error messages, became totally useless if caught within the scope of LOOPS high level procedures, or new versions of logical procedures. I have tried to make this as safe as I could. You should still be able to run and test standard Logo programs, and even write new programs that will behave in a standard way. Nonetheless, note--LOOPS is *not* thoroughly tested!  File: loops.info, Node: ENTERING AND LEAVING LOOPS, Prev: OVERVIEW, Up: INTRODUCTION Entering and Leaving LOOPS ========================== LOOPS was developed and tested only on Free-BSD and GNU-Linux. For non UNIX OSs, I only assume that this is the right way to start. The process to start LOOPS depends on your operating system: `DOS:' Change directories to the one containing Logo (probably C:\UCBLOGO). Then type UCBLOGO for the large memory version. LOOPS will not run on 640K. When Logo is running, load LOOPS.LG file with LOAD command. You will have to copy additional library files manually to the directory which holds standard Logo library. However I have never tried this, so--chances are, LOOPS will not work on DOS at all. `Mac:' Double-click on the LOGO icon within the "UCB Logo" folder, and load loops.lg file with LOAD command--I hope? You will have to copy additional library files manually to the directory which holds standard Logo library. `Windows:' Double-click on the UCBWLOGO icon in the UCBLOGO folder. When Logo is running, load LOOPS.LG file with LOAD command. To exit LOOPS and restore standard ucblogo, type RESTORE.STANDARD command. You will have to copy additional library files manually to the directory which holds standard Logo library. However, working with LOOPS in Windows will be difficult due to the Lisp like structure of LOOPS code. You REALLY need an editor (like *Emacs*) that recognizes Logo syntax and indentation rules. Unfortunately, although Emacs is available for Windows, LOGO-MODE is not. You might try using LOOPS with MSWLogo--it should work, but you still have the same problem with the editor--it is simply not good enough. `Unix:' Type the word logo to the shell. (The directory in which you've installed Logo must be in your path.) then load .loops file with LOAD command. These are just first aid instructions. The right way to do this follows: Install *Emacs logo-mode* (v2.7 or later). This will automatically install all parts of LOOPS in proper directories. Library additions will be added to /usr/local/lib/logo/logolib, their help files to /usr/local/lib/logo/helpfiles and .loops into your home directory. Your .emacs file will also be updated accordingly. On completion of installation, you will be offered to work through logo-mode tutorial--do it. All of LOOPS features, including starting and exiting cleanly, are available from Emacs menus in logo-mode.  File: loops.info, Node: CHANGES TO STANDARD LOGO PROCEDURES, Next: ADDITIONS TO LOGO, Prev: INTRODUCTION, Up: Top Changes to Standard Logo Procedures *********************************** One of the differences between Logo and other Lisp dialects, and the one that affects programming style, is what Logo accepts as true. Logo's idea of truth is only the word `"true'. Likewise, false is only the word `"false'. Anything else--handed as the first input to control structures results in error. Therefore the base change introduced in loops is: ALL RETURNED VALUES ARE CONSIDERED TRUE, EXCEPT THE WORD `"false'. Word `"true' has no special meaning in this context. Following control structures have been altered to accept this: IF, IFELSE, and TEST. IFTRUE, IFFALSE, WHILE, UNTIL DO.WHILE, and DO.UNTIL have not been altered, but work nonetheless by virtue of using internally either IF or TEST. * Menu: * CONTROL:: * LOGICAL OPERATIONS:: * EXTENDED PREFIX OPERATORS::  File: loops.info, Node: CONTROL, Next: LOGICAL OPERATIONS, Prev: CHANGES TO STANDARD LOGO PROCEDURES, Up: CHANGES TO STANDARD LOGO PROCEDURES Control ======= * Menu: * IF:: * IFELSE:: * TEST:: * dAND:: AND  File: loops.info, Node: IF, Next: IFELSE, Prev: CONTROL, Up: CONTROL if -- IF anyvalue instructionlist (IF anyvalue instructionlist1 instructionlist2) command. If the first input has any value except FALSE, then IF runs the second input. If the first input has the value FALSE, then IF does nothing. (If given a third input, IF acts like IFELSE, as described below.) It is NOT an error if the first input is not either TRUE or FALSE. For compatibility with earlier versions of Logo, if an IF instruction is not enclosed in parentheses, but the first thing on the instruction line after the second input expression is a literal list (i.e., a list in square brackets), the IF is treated as if it were IFELSE, but a warning message is given. If this aberrant IF appears in a procedure body, the warning is given only the first time the procedure is invoked in each Logo session. Standard Logo IF is renamed IF.TF  File: loops.info, Node: IFELSE, Next: TEST, Prev: IF, Up: CONTROL ifelse ------ IFELSE anyvalue instructionlist1 instructionlist2 command or operation. If the first input has any value except FALSE, then IFELSE runs the second input. If the first input has the value FALSE, then IFELSE runs the third input. IFELSE outputs a value if the `instructionlist' contains an expression that outputs a value. Standard Logo IFELSE is renamed IFELSE.TF  File: loops.info, Node: TEST, Next: dAND, Prev: IFELSE, Up: CONTROL test ---- TEST anyvalue command. Remembers its input, which can be anything, for use by later IFTRUE or IFFALSE instructions. The effect of TEST is local to the procedure in which it is used; any corresponding IFTRUE or IFFALSE must be in the same procedure or a subprocedure. Standard Logo TEST is renamed TEST.TF  File: loops.info, Node: dAND, Prev: TEST, Up: CONTROL .and ---- (.and instructionlist) .and instructionlist1 instructionlist2 (.and instructionlist1 instructionlist2 ...) operation. Runs `instructionlist's one by one until one returns FALSE, in which case .and immediately returns FALSE. If none return FALSE, .and outputs the result of last `instructionlist'. The only difference with AND, AND THAT IS THE REASON FOR CLASSIFYING .AND AS A CONTROL STRUCTURE is that .AND accepts even *nothing* as TRUE. Therefore `instructionlist' does not have to return any value, and still be accepted as TRUE. In the code that follows, the third instructionlist `[make :var1 text :var]' will be accepted as TRUE: (.and [wordp :var] [definedp :var] [make :var1 text :var] ...) If last `instructionlist' does not output, word `#unspecified' will be returned.  File: loops.info, Node: LOGICAL OPERATIONS, Next: EXTENDED PREFIX OPERATORS, Prev: CONTROL, Up: CHANGES TO STANDARD LOGO PROCEDURES Logical Operations ================== Both--Standard Logo, and LOOPS logical operators `and' and `or' use same names, but they differ in the range of syntax they accept. Standard Logo `and' and `or' MUST have words, as first inputs (this is rather inaccurate description--they actually accept ONLY words `true' and `false'), while LOOPS `and' and `or' accept lists and/or words as inputs. This is also inaccurate--if you want to use LOOPS functionality (serial execution and any value accepted as `true') you MUST quote (protect with square brackets) all inputs. To preserve compatibility with standard Logo code, LOOPS `and/or' accept also words as inputs, but then Logo rules must be adhered to; value of inputs MUST be only words `true', and `false'. LOOPS `and/or' accept even mixed syntax of words and lists as inputs, however this is discouraged. *Note CODE EFFICIENCY::. As LOOPS `and' and `or' do not necessarily evaluate ALL of their inputs, they can be used as control structures, just like in Lisp. `or' returns immediately on first success. `and' returns immediately on first failure. However, note that (unlike Logo and LOOPS IF and TEST), both Logo and LOOPS OR AND and .AND , can't properly handle code with side effects, and results can be unexpected. Take a look at following two procedures: to and.logo.test pr namep "a pr and run [localmake "a 1 pr :a "true] namep "a end to and.loops.test pr namep "a pr and [localmake "a 1 pr :a "true] [namep "a] end and.logo.test is using standard Logo AND, while and.loops.test is using LOOPS AND . When run, both will give following output: ? and.logo.test false 1 false ? and.loops.test false 1 false Obviously, in both cases, the block of code with side effects [localmake "a 1 pr :a "true], had no visible effect outside that block, otherwise the third line of output should have been TRUE in both cases. * Menu: * AND:: * OR:: * NOT::  File: loops.info, Node: AND, Next: OR, Prev: LOGICAL OPERATIONS, Up: LOGICAL OPERATIONS and --- Logo: AND tf1 tf2 (AND tf1 tf2 tf3 ...) LOOPS: (AND) (AND instructionlist) AND instructionlist1 instructionlist2 (AND instructionlist1 instructionlist2 ...) LOOPS version of AND is a simulation of AND special form--that is it does not evaluate all of its inputs if not needed. It starts evaluating from left to right, and returns FALSE immediately after one of the inputs evaluates to FALSE. If none of inputs evaluate to false, the value of the last input is returned. All `instructionlist's MUST be operations. If you want to mix operations and commands use .AND instead. Standard Logo AND is renamed AND.TF Use AND.TF only on speed sensitive code where serial evaluation is not required.  File: loops.info, Node: OR, Next: NOT, Prev: AND, Up: LOGICAL OPERATIONS or -- Logo: OR tf1 tf2 (OR tf1 tf2 tf3 ...) LOOPS: (OR) (OR instructionlist) OR instructionlist1 instructionlist2 (OR instructionlist1 instructionlist2 ...) outputs whatever is returned by the first successful evaluation of `instructionlist', or the word FALSE if all `instructionlist's fail. All `instructionlist's MUST be operations. Standard Logo OR is renamed OR.TF Use OR.TF only on speed sensitive code where serial evaluation is not required.  File: loops.info, Node: NOT, Prev: OR, Up: LOGICAL OPERATIONS not --- NOT anyvalue outputs TRUE if the input is FALSE, and FALSE if the input is any value but FALSE. Standard Logo NOT is renamed NOT.TF  File: loops.info, Node: EXTENDED PREFIX OPERATORS, Prev: LOGICAL OPERATIONS, Up: CHANGES TO STANDARD LOGO PROCEDURES Extended Prefix Operators ========================= There are some minor changes to following prefix operators: DIFFERENCE QUOTIENT BEFORE? EQUAL? GREATER? and LESS? . They now accept more than two arguments. However note that BEFOREP EQUALP GREATERP and LESSP have NOT been changed. Only those ending in `?' are changed. Logo DIFFERENCE and QUOTIENT are still available under following names: `difference.s' and `quotient.s' I am not sure how useful this is. What I REALLY wanted to do was to change Logo infix operators `+ - * /' to prefix versions, so that following syntax could be used: print (+ 1 2 3 4 5) => 15 This can easily be done for `+ * /', but `-' is a problem. Logo parser is handling `-' differently than other operators, so--if I can't change `-', I decided not to change anything. It would have been nice though, to be able to write `(- 5 2 1)', than to have to write `(DIFFERENCE 5 2 1)'. * Menu: * DIFFERENCE:: * QUOTIENT:: * BEFORE?:: * BEFOREQ?:: * EQUAL?:: * GREATER?:: * GREATEREQ?:: * LESS?:: * LESSEQ?::  File: loops.info, Node: DIFFERENCE, Next: QUOTIENT, Prev: EXTENDED PREFIX OPERATORS, Up: EXTENDED PREFIX OPERATORS difference ---------- (difference number) difference number1 number2 (difference number1 number2 ...) operation. Outputs -NUMBER, or NUMBER1-NUMBER2- ... Logo `difference' is available as `difference.s'  File: loops.info, Node: QUOTIENT, Next: BEFORE?, Prev: DIFFERENCE, Up: EXTENDED PREFIX OPERATORS quotient -------- (quotient number) quotient number1 number2 (quotient number1 number2 ...) operation. Outputs 1/NUMBER, or NUMBER1/NUMBER2/... Logo `quotient' is available as `quotient.s'  File: loops.info, Node: BEFORE?, Next: BEFOREQ?, Prev: QUOTIENT, Up: EXTENDED PREFIX OPERATORS before? ------- (before? word) before? word1 word2 (before? word1 word2 ...) operation. Outputs `true' or `false': (before? "a) => true before? "a "b => true (before? "a "b "c "c) => false  File: loops.info, Node: BEFOREQ?, Next: EQUAL?, Prev: BEFORE?, Up: EXTENDED PREFIX OPERATORS beforeq? -------- (beforeq? word) beforeq? word1 word2 (beforeq? word1 word2 ...) operation. Outputs `true' or `false': (beforeq? "a) => true beforeq? "b "a => false (beforeq? "a "b "c "c) => true  File: loops.info, Node: EQUAL?, Next: GREATER?, Prev: BEFOREQ?, Up: EXTENDED PREFIX OPERATORS equal? ------ (equal? input) equal? input1 input2 (equal? input1 input2 ...) operation. Outputs `true' or `false': (equal? 1) => true equal? "a "b => false (equal? 1 1 1) => true  File: loops.info, Node: GREATER?, Next: GREATEREQ?, Prev: EQUAL?, Up: EXTENDED PREFIX OPERATORS greater? -------- (greater? number) greater? number1 number2 (greater? number1 number2 ...) operation. Outputs `true' or `false': (greater? 1) => true greater? 1 1 => false (greater? 3 2 1) => true  File: loops.info, Node: GREATEREQ?, Next: LESS?, Prev: GREATER?, Up: EXTENDED PREFIX OPERATORS greatereq? ---------- (greatereq? number) greatereq? number1 number2 (greatereq? number1 number2 ...) operation. Outputs `true' or `false': (greatereq? 1) => true greatereq? 1 2 => false (greatereq? 3 2 2) => true  File: loops.info, Node: LESS?, Next: LESSEQ?, Prev: GREATEREQ?, Up: EXTENDED PREFIX OPERATORS less? ----- (less? number) less? number1 number2 (less? number1 number2 ...) operation. Outputs `true' or `false': (less? 1) => true less? 1 1 => false (less? 1 2 3) => true  File: loops.info, Node: LESSEQ?, Prev: LESS?, Up: EXTENDED PREFIX OPERATORS lesseq? ------- (lesseq? number) lesseq? number1 number2 (lesseq? number1 number2 ...) operation. Outputs `true' or `false': (lesseq? 1) => true lesseq? 1 1 => true (lesseq? 1 2 1) => false  File: loops.info, Node: ADDITIONS TO LOGO, Next: ADDITIONS TO LOGO LIBRARY, Prev: CHANGES TO STANDARD LOGO PROCEDURES, Up: Top Additions to Logo ***************** The main reason for these additions being here, and not in the additions to Logo Library--is that they all require COMPILER to achieve reasonable performance. Although (e.g., CASE) itself can be loaded from the library on demand, compiler can not. Emacs logo-mode needs the whole block of code loaded before it will display menus to operate the compiler. * Menu: * CODE EFFICIENCY:: * LOCAL PROCEDURES:: * CONTROL STRUCTURES:: * COMPILER:: * RESTORING STANDARD LOGO::  File: loops.info, Node: CODE EFFICIENCY, Next: LOCAL PROCEDURES, Prev: ADDITIONS TO LOGO, Up: ADDITIONS TO LOGO Code Efficiency =============== When talking about performance--first, it should be obvious that programs written with LOOPS code (but without using OOPS--that is another story altogether) WILL run slower than code written in standard Logo. However, in certain situations, even simple LOOPS code can match, and at times even beat (by order of magnitude) standard Logo. By SIMPLE, I mean the code written by a beginner. It is should be obvious that the same level of performance MUST be achievable in standard Logo as well, it is just unlikely, that a beginner will know how to. Let's have a look at one such extreme case: The problem consists of stepping down a very long list and performing a time consuming operation on each item. This TIME CONSUMING operation returns either a value, or "false. If a value is returned, this value should be the final result of the procedure. This is a simple version written in Logo: to speed :list if emptyp :list [op "false] local "tmp make "tmp very.expensive.operation first :list if not equalp :tmp "false [op :tmp] op speed bf :list end This one is written in loops code: to speed :list condc [ [[emptyp :list] [op "false]] [[very.expensive.operation first :list]] [else [op speed bf :list]]] end On a sufficiently large list, loops code will be an order of magnitude faster. Let's try one popular Lisp example, procedure flat? (lat?): Written in standard Logo to flat? :lst if empty? :lst [op "true] op and word? first :lst flat? bf :lst end When this predicate is applied to list `[1 2 3 ... 10000]', it will run very fast--let's call it factor 1. Written in loops code: to flat? :lst if empty? :lst [op "true] op and [word? first :lst] [flat? bf :lst] end This will run (applied to the same list) with a factor of about 2.2, much slower. However when both are applied to the list `[[1] 2 3 ... 10000]', first (Logo) code will still run at factor 1. Loops code will complete this in practically factor 0, much faster. Even a judicious mix of loops `and/or', and Logo `and/or', can yield factor 0, but be careful which input to `and' you quote, and which one not. *Nonetheless do not mix!* There are no speed benefits in it, just an increased margin for error. to flat? :lst if empty? :lst [op "true] op and word? first :lst [flat? bf :lst] end This will run at factor 0, but if you reverse quoting, it will run at 2.2 .  File: loops.info, Node: LOCAL PROCEDURES, Next: CONTROL STRUCTURES, Prev: CODE EFFICIENCY, Up: ADDITIONS TO LOGO Local Procedures ================ LOOPS uses LETREC SPECIAL FORM for providing local procedures in Logo. This is not a complete truth, as Logo does not provide means for creating real special forms. That is why LETREC syntax looks almost like Lisp syntax, wrapping its input in lists, to protect it from evaluation. LETREC takes one input, a list of this format: LETREC [[[PROC-NAME1 [PROC-TEXT1]] [PROC-NAME2 [PROC-TEXT2]] [ ... ]] [BODY-FORM1] [BODY-FORM2] [ ... ]] `PROC-NAME' is an ordinary word. `[PROC-TEXT]' is a list of lists in the form that Logo's TEXT outputs, or that DEFINE accepts as input. `[BODY-FORM]' is instruction list (ordinary Logo instruction line). Block of BODY-FORMs functions as instruction lines in the body of TO definition, except that each line must be a list. There are no restrictions as to what can be done with BODY-FORMs, but their main task is to call procedures defined as `PROC-NAME's. LETREC outputs if one of BODY-FORMs outputs. All procedures defined this way are local to LETREC, and not to the context from where LETREC was invoked. That is, there is no way to invoke PROC-NAME outside of LETREC. Order in which procedures are defined is NOT important. BODY-FORMs can call all PROC-NAMEs, and all procedures can call each other from their PROC-TEXTs without restrictions. Here is one example, that illustrates the statement above. A simple quick-sort, written with `letrec': to quick.sort :lst op letrec [[ [sort [[lst] [if or [emptyp :lst] [emptyp bf :lst] [op :lst]] [op (split first :lst [] [] bf :lst)]]] [split [[pivot left right list] [if emptyp :list [op (se (sort :left) :pivot (sort :right))]] [if lessp first :list :pivot [op (split :pivot fput first :list :left :right bf :list)]] [op (split :pivot :left fput first :list :right bf :list)]]]] [op (sort :lst)]] end The two local procedures `sort' and `split', are mutually recursive, and are calling each other. Reverting back to the performance; this code is considerably slower (`letrec' is relatively slow) than code written in standard Logo, using two global procedures: to quick.sort :lst if emptyp :lst [op []] if emptyp bf :lst [op :lst] op split first :lst [] [] bf :lst end to split :pivot :left :right :list if emptyp :list [op (se quick.sort :left :pivot quick.sort :right)] if lessp first :list :pivot [ op split :pivot fput first :list :left :right bf :list] op split :pivot :left fput first :list :right bf :list end If we recode loops example using `cond', performance will improve, but it can not match native Logo code: to quick.sort :lst op letrec [[ [sort [[lst] [condc [ [[or [emptyp :lst] [emptyp bf :lst]] [op :lst]] [else [op (split first :lst [] [] bf :lst)]]]]]] [split [[pivot left right list] [condc [ [[emptyp :list] [op (se (sort :left) :pivot (sort :right))]] [[lessp first :list :pivot] [op (split :pivot fput first :list :left :right bf :list)]] [else [op (split :pivot :left fput first :list :right bf :list)]]]]]]] [op (sort :lst)]] end Names in LETREC do not follow Logo, but Scheme rules. You can not use the same word for variable and procedure name. That is if you had a local (or global) variable SOME-VAR defined before LETREC, you can not use the name SOME-VAR for PROC-NAME. Actually, you can, but the variable will be shadowed during the life-span of LETREC. Likewise, it is an error to use PROC-NAME as a name of a local variable, later in the body of LETREC. However you can use the name of a previously defined global procedure as a name for PROC-NAME, but that is not advisable. If you do that, you should be very careful not to place a call to global procedure in parentheses. All that I have said here can be summed up with this: PROC-NAMEs are actually not real procedures, but local variables. The only special syntax rule when calling PROC-NAME within BODY-FORMs or PROC-TEXT is that call MUST be enclosed in parenthesis: (PROC-NAME :input1 :input2 ...) The following FUNNY bit of code shows that tokenization is not an issue. It also shows that using global procedure names for PROC-NAMEs is acceptable, as long as you observe the rules from previous paragraph--do not parenthesize global names. to tokenize.test :n letrec [[ [random [[] [localmake "random.num sum random :n 1] [pr :random.num] [op :random.num]]]] [(pr "|result = | ((3*((random)+(random)))/(random)+(random)))]] end Here is one short example of programming style that uses local procedures. Whether this style is elegant, or not is irrelevant here. What is important is that apart from LETREC, the whole construction relies heavily on AND and OR--both for decision making, and result returning. Standard Logo can not function this way. 8 queens ;; Start queens with queens 8, if you want only the first result ;; To get all results use (queens 8 "false), and be prepared for ;; a long wait. (Maybe you should try 6, or 4 first?) to queens :number [:one.only "true] 1 ;; interpreter/compiler mark pr [started with letrec] (foreach letrec [[ ;; Define local procedure makeboard [makeboard [[n] [localmake "row cascade :n [[x] [op lput # :x]] []] [op cascade :n [[x] [op fput :row :x]] []]]] ;; Define local procedure shade [shade [[queen rest.board [offset 1] 2] [if emptyp :rest.board [op []]] [op (fput (filter [[x] [op not (or :x = (:queen - :offset) :x = :queen :x = (:queen + :offset))]] first :rest.board) (shade :queen bf :rest.board :offset + 1))]]] ;; Define local procedure put.queen [put.queen [[board result] ;; When the board is empty, we're done [if emptyp :board [push "stack :result op :one.only]] [localmake "row first :board] [localmake "queen and [not emptyp :row] [first :row]] ;; If queen not placed -- backtrack right away [op (and [:queen] [or ;; Go ahead -- try to place next queen [(put.queen (shade :queen bf :board) lput :queen :result)] ;; Nowhere to put next queen -- try to move ;; this queen to next available square [(put.queen fput bf :row bf :board :result)]])]]]] ;; Start of letrec body forms ;; interpreter/compiler mark [pr [now running queens]] [localmake "stack []] ;; Run program and discard the output (true/false) [ignore (put.queen (makeboard :number) [])] ;; Output positions -- letrec ends here [op reverse :stack]] ;; Template for foreach [[x] [(show "position # :x)]]) end You will have noticed that I use both styles for `and/or' here. `or' in SHADE is standard Logo `or'. All `and/or's in PUT.QUEEN are LOOPS. What kind of `or' is used in SHADE is irrelevant--however PUT.QUEEN will not function if standard style is used.  File: loops.info, Node: CONTROL STRUCTURES, Next: COMPILER, Prev: LOCAL PROCEDURES, Up: ADDITIONS TO LOGO Control Structures ================== Use `cond', `condc', and `case' wherever possible. They are implemented very efficiently, and at the worst will match standard Logo code (`condc'). `Cond', and `case' will be marginally slower than hand written code, but only if you remember to use Logo's native `if.tf', and `ifelse.tf'. If you use loops `if', and `ifelse' for hand written code, `cond', and `case' will be faster. * Menu: * COND:: * CONDC:: * CASE:: * CASEC::  File: loops.info, Node: COND, Next: CONDC, Prev: CONTROL STRUCTURES, Up: CONTROL STRUCTURES cond ---- COND takes one input, a list of this format: COND [ [[CONDITIONAL1] [TRUE1a] [TRUE1b] ...] [[CONDITIONAL2] [TRUE2a] ... ] [ ... ] [else [CATCH-ALL] ... ]] Briefly, COND works like nested IFELSEs, but with some refinements. Each clause in COND should consist of two parts. First is CONDITIONAL expression. If CONDITIONAL expression evaluates to true, the corresponding TRUE forms are executed. Each clause can have more than one TRUE form, and they look (but do not function) like body forms in LETREC, which means that they are NOT Logo instruction lines. For run-time efficiency reasons, compiler appends all TRUE forms (belonging to the same CONDITIONAL) into one Logo instruction line. Each TRUE form MUST be a list. CONDITIONAL expression can be a word, but only one word. COND will try all CONDITIONAL expressions until it finds one that returns true value. Then it will execute its TRUE forms and return. If all CONDITIONAL expressions evaluate to FALSE, else part is executed. (ELSE is just a procedure returning TRUE.) CATCH-ALL forms are just another name for TRUE forms, and the same rules apply. If you omit ELSE clause, then word `#unspecified' is returned. COND ALWAYS returns a value, therefore if you do not need this value, you should pipe the output into `IGNORE', as in `(... ignore cond [...] ...)'. Value returned by COND is either the output of one of TRUE forms, or word `#unspecified', if your TRUE forms are not returning any values. It stands to reason that all of your TRUE forms should output, or none. This gets somewhat more complicated, as COND can be OPERATION, or COMMAND. When you want to use `cond' as operation (and this is ALWAYS), you should *not* use `op/output' command in your TRUE forms. If you adhere to this rule, `cond' will return a value that can be used in the calling procedure (e.g.: `make "var cond [...] ... more-code'). Likewise, it can be used as a result of the calling procedure, (e.g.: `op cond [...] end'), or it can be ignored (as mentioned above). To use `cond' as a command (command in the same sense as the Logo command `output'), use `op' in all TRUE forms, but then `cond' will output from the calling procedure. It is an error to use `output', or `make "var' before `cond' in this case. However, as I said before, I do not recommend using `cond' as a command, as this would invalidate the next paragraph. Exceptionally, any COND clause can consist only of CONDITIONAL expression without TRUE form. In that case, if CONDITIONAL evaluates to any value other than FALSE, that value is returned by COND, or if FALSE, COND moves on to the next clause. Note that COND internally uses Logo IFELSE.TF for checking CONDITIONAL expressions, as long as CONDITIONAL is followed by at least one TRUE form. If there are no TRUE forms LOOPS IF is used, as in that case it MUST be able to return values other than the word TRUE. One simple COND example: cond 1 to cond.memberp :element :list op cond [ [[emptyp :list] ["false]] [[equalp first :list :element]] [else [cond.memberp :element bf :list]]] end Note that `cond' is here used as an OPERATION, and does need `output' before it. Actually to write COND.MEMBERP procedure with explicit `op' in TRUE forms, would be an error. cond 2 to cond.memberp :element :list cond [ [[emptyp :list] [op "false]] [[equalp first :list :element]] [else [op cond.memberp :element bf :list]]] end This is what happens when you run this with: pr cond.memberp 3 [1 2 3 4] You don't say what to do with true in cond.memberp  File: loops.info, Node: CONDC, Next: CASE, Prev: COND, Up: CONTROL STRUCTURES condc ----- `Condc' is a *command* version of `cond'. It was added because it is slightly more efficient than `cond', (uses `if.tf', rather than nested `ifelse.tf' internally. It must be used with explicitly written `op' in TRUE forms. Using `op' before `condc' is an error (equal to writing `op op something'. This is the correct code for cond.memberp example, using `condc': to condc.memberp :element :list condc [ [[emptyp :list] [op "false]] [[equalp first :list :element]] [else [op condc.memberp :element bf :list]]] end By the way; `condc' stands for cond-command. Both cond.memberp and condc.memberp examples here are coded without TRUE form in the second COND clause `[[equalp first :list :element]]' as an illustration only. Coding this clause with `[[equalp first :list :element] [op "true]]' would be more efficient.  File: loops.info, Node: CASE, Next: CASEC, Prev: CONDC, Up: CONTROL STRUCTURES case ---- CASE takes one input, a list of this format: CASE [ [TARGET-EXPR] [[KEYS1] [TRUE1a] [TRUE1b] ...] [[KEYS2] [TRUE2a] ... ] [ ... ] [else [CATCH-ALL] ... ]] CASE works almost like COND with some minor differences. First clause in CASE (TARGET-EXPR) is Logo expression that must return a value, which I will call the KEY. This KEY is then checked (with MEMBERP) against all KEYS. KEYS1, KEYS2... are either lists of values (sets) or words. Bear in mind that each of the KEYS can, if word, be one word only. When one expression MEMBERP KEY KEYS returns "true, than its TRUE forms are executed, and their result is returned by CASE. If all KEYS expressions evaluate to FALSE, else part is executed. CATCH-ALL forms are just another name for TRUE forms, and same rules apply. If you omit ELSE clause, then word `#unspecified' is returned. CASE, just like COND ALWAYS returns a value, therefore if you do not need this value, you should pipe the output into `IGNORE', as in `(... ignore case [...] ...)'. Value returned by CASE is either the output of one of TRUE forms, or word #UNSPECIFIED, if your TRUE forms are not returning any values. This (just as with `cond') gets more complicated, as CASE can be OPERATION, or COMMAND. When you want to use `case' as operation, you should *not* use `op/output' command in your TRUE forms. If you adhere to this rule, `case' will return a value that can be used in the calling procedure (e.g.: `make "var case [...] ... more-code'). Likewise, it can be used as a result of the calling procedure, (e.g.: `op case [...] end'), or it can be ignored (as mentioned above). To use `case' as a command (command in the same sense as the Logo command `output'), use `op' in all TRUE forms, but then `case' will output from the calling procedure. It is an error to use `output', or `make "var' before `case' in this case. You should be careful when using word for KEYS expression. If used to compare numbers, the result may not be what you expect. For example; KEY is 1 and KEYS is 3210 , will return TRUE. This is OK if you intended to check whether 1 is a member of range 3 2 1 0 , but if what you really want is to check whether the KEY is equal to number 3210 , then use [3210] for KEYS. Likewise it is OK to use word for KEYS if you use a single letter for KEY (KEYS= "abcd KEY= "b), but if you use a word like "ab for KEY, then you have to use a list of words for KEYS. Rules for constructing TARGET-EXPR: TARGET-EXPR can be a constant, a variable reference or a Logo instruction line. It can even be a recursive operation (if you use LETREC). However remember that it must return a value. Following are legal TARGET-EXPRs: `["a]' , `["House]' , `[123]' , `[:var.name]' , `[ifelse :var.name > 10 ["a] ["b]]' , `[reduce "sum :lst]' , `[[1 2 3]]' . In this last one the value of TARGET-EXPR is a list [1 2 3]. Note that word constants (unless representing numbers) *must be quoted*, otherwise Logo will try to run them as a procedure. There is only one restriction to remember: You can use LOCALMAKE in TARGET-EXPR but this variable is then visible only in TARGET-EXPR block. What this means is; any reference to that local variable in the rest of the CASE body will yield an unbound variable error. If you really need this, declare the local variable (with `local "var.name') before invocation of CASE. In TARGET-EXPR, you can use MAKE to give the `var.name' a value. This way `var.name' will be visible throughout the CASE. The following example explains most of what was said above: to case.test :x local "temp case [ [make "temp random :x ifelse :temp > 10 [char sum 97 random 26] [:temp]] [[2 4 6] [pr se :temp :#target] [op "even]] [[1 3 5] [pr se :temp :#target] [op "od]] [0 [op "zero]] ["abcdefghijklmnopqrstuvwxyz [pr se :temp :#target] [op "letter]] [else [pr (se "missed :temp :#target)] [op case.test :x-1]]] end Variable #TARGET is local variable available during lifespan of CASE. It represents the real value of KEY. In the example above, `temp' also represents the value of KEY, but not always. Try running `pr case.test 15' repeatedly, to get the feeling of what I'm talking about.  File: loops.info, Node: CASEC, Prev: CASE, Up: CONTROL STRUCTURES casec ----- `casec' is a *command* version of `case'. It was added for a different reason than `condc'. CONDC is slightly more efficient than COND, but `casec' is needed to handle objects that (most of the time) do not output any values--as in OOP with turtles. As all objects eventually need to output something(1) `casec' is able to handle this. It must be used with explicitly written `op' in TRUE forms, but *only* those that will eventually output a value. All other TRUE forms do not have to use explicit output(2). Using `op' before `casec' is an error (equal to writing `op op something'). Here is an example with typical use of `casec': to casec.test :x casec [ [:x] [[1 3 5] [pr "odd]] [[2 4 6] [pr "even]] ["abc [pr "letter]] [else [op "whatever]]] end Note, that the first three TRUE forms do not output anything (`op "#unspecified' will be appended by the compiler). The `else' clause, however, must use `op', as it does output the word WHATEVER. CASEC is a command, in the same sense the `output' is, therefore it will always output from the calling procedure. ---------- Footnotes ---------- (1) Delegated messages always output. (2) Explicit `output "#unspecified' will be appended by the `casec'.  File: loops.info, Node: COMPILER, Next: RESTORING STANDARD LOGO, Prev: CONTROL STRUCTURES, Up: ADDITIONS TO LOGO Compiler ======== * Menu: * USING THE COMPILER:: * EXTENDING THE COMPILER:: * SAVING COMPILED PROCEDURES:: After introducing Lisp structures LETREC, COND, and CASE, and due to inefficient implementation of Logo MACRO, it soon became obvious that the compiler is a necessity. Compiler is doing the job of Lisp Macro--expanding the macro code, and replacing LETREC, COND, CONDC, and CASE structures with standard Logo code. When Logo interpreter finally runs the procedures that use LETREC, COND, and CASE, it never sees their Lisp like structure, but only standard Logo instructions. If you observe closely what happens when queens program is run (with `queens 8', you will notice that there is a waiting period between `started with letrec', and `now running queens' printouts. Then comes the long wait for positions to print out. There is nothing I can do about waiting for Logo to find positions, but the first waiting period is another story. During this time interpreter is not actually running queens, but rather interpreting letrec structure into a usable form. If you tried to run COND example, the situation was even worse. `cond.memberp' is a recursive procedure, and Logo interpreted COND structure in every invocation of `cond.memberp'. That is prohibitively expensive. The answer to this is, of-course, High Level Structure Compiler.  File: loops.info, Node: USING THE COMPILER, Next: EXTENDING THE COMPILER, Prev: COMPILER, Up: COMPILER Using the Compiler ------------------ loops.compiler [ proc1 proc2 ...] loops.compiler "proc (loops.compiler) `loops.compiler' takes one input, a list of procedure names, or one procedure name. If you want to compile the whole workspace, call it without inputs: To try it out do: loops.compiler "queens Logo should respond with: compiling queens ... OK compilation completed Now run queens again. Both STARTED WITH LETREC, and NOW RUNNING QUEENS should print out without waiting this time. Compiled `cond.memberp' example should likewise equal native Logo performance. `loops.compiler' can handle very complex structures, so don't be afraid to experiment--with nested letrec, for instance. You can define letrec within another letrec definition, or within cond, or case, or vice-versa. Same scope rules apply--you can NOT call inner PROC-NAMEs from outer. Pay attention, however, to syntax rules mentioned before (local procedure calls MUST be placed in parenthesis). One simple TORTURE test for compiler is converting ordinary recursive `insert.sort' to a non-recursive version, using `cond' inside `letrec', inside lambda, as a first input to `reduce'. In case you wonder why this `lput (list last :lst) bl :lst' as a second input to reduce (instead of just using `:lst') -- this is to prevent `reduce' (actually `insert') to `butfirst' through last number in :LST as if it were a word. to insert.sort :lst op (reduce [[e lst] [op letrec [[ [insert [[lst] [condc [ [[emptyp :lst] [op (list :e)]] [[:e < first :lst] [op fput :e :lst]] [else [op fput first :lst (insert bf :lst)]]]]]]] [op (insert :lst)]]]] lput (list last :lst) bl :lst) end There is another way to start the compiler: ws.compile This is meant to be used from LOGO-MODE with `Compile Workspace' menu, but can also be used manually. `ws.compile' is a procedure that runs a filter on all Logo procedures in workspace and hands down to the compiler only those that do NOT end in WHATEVER`.class' . Procedures ending in `.class' are LOOPS classes, and are NOT supposed to be compiled. The trouble is that most of them contain LOOPS control structures (CASE), and loops.compiler would compile them otherwise. In case you want to use the compiler from a program, and do not want to see the compiler progress printout, set the following variable to `false': make ".verbose "false localmake ".verbose "false The global variable `.verbose' is set to `true' on LOOPS startup. The second version (with `localmake' is preferable, to using `make', because you will have used it from one of your procedures before calling the compiler, and therefore only shadowing the global variable `.verbose'. There will be no need to restore it later.  File: loops.info, Node: EXTENDING THE COMPILER, Next: SAVING COMPILED PROCEDURES, Prev: USING THE COMPILER, Up: COMPILER Extending the Compiler ---------------------- Compiler is designed to be easily extended. So far, it knows that it has to compile LETREC, COND, CONDC and CASE. Note the difference HAS TO, as opposed to KNOWS HOW TO. The actual compilation (better word would be CODE EXPANSION) is done by the Logo library procedure `macroexpand', using transformer procedures for LETREC CASE and COND. Compiler capabilities (knowledge what to compile) is stored in the PROPERTY `compiler.cap', which is loaded on LOOPS startup. Capabilities have this format: property = name of High Level Structure value = "false and their current state is: pprop "compiler.cap "letrec "false pprop "compiler.cap "cond "false pprop "compiler.cap "case "false pprop "compiler.cap "condc "false You can add your own compiler capabilities, providing you follow these rules: High level procedure (structure) you define must have one input only--a list. And, of-course it must be a macro, otherwise there is nothing for compiler to do. After you have defined (and tested) your macro transformer procedure, add your pprop line to lines above. That is all. Writing macro transformer is rather difficult (at least the kind that would require `loops.compiler', and giving instructions on how to do this, is certainly beyond the scope of this manual. The best advice I can give is: read COMPUTER_SCIENCE_LOGO_STYLE, VOLUME_1_2_3 BY BRIAN HARVEY, and then take a look at LETREC, COND and CASE transformer code in `.loops'.  File: loops.info, Node: SAVING COMPILED PROCEDURES, Prev: EXTENDING THE COMPILER, Up: COMPILER Saving Compiled Procedures -------------------------- If you want to save compiled versions of procedures do: save.compiled "filename. This will save ALL COMPILED procedures that exist in the Logo workspace at that moment. It will NOT save procedures written in standard Logo. The idea is that you can develop a program with a mix of LOGO and LOOPS procedures, compile LOOPS procedures, and save them to a file. Then comment out their source in the original source file, copy compiled versions from the file you saved them to, and paste them into the original source file. This way, when you load the source next time, only the compiled versions get actually loaded. I do recommend that you keep the source (commented out), just in case you want to change something later. Changing the compiled code (although that is a valid Logo code), can prove to be difficult. This is exactly what .loops file looks like; a mix of standard Logo procedures, LOOPS procedures (commented out), and compiled LOOPS procedures at the end.  File: loops.info, Node: RESTORING STANDARD LOGO, Prev: COMPILER, Up: ADDITIONS TO LOGO Restoring Standard Logo ======================= This has practical value only when used from EMACS LOGO-MODE. You can use it to change from standard Logo to LOOPS and vice versa quickly (with the click of the mouse), but it can also be used manually: restore.standard => Standard Berkeley Logo Restored. This will erase all LOOPS variables and properties (including object static storage), erase all additional LOOPS procedures and structures, and restore all standard Logo procedures that were redefined by LOOPS. It will NOT erase additions to Logo library, as they can be loaded on demand, and are coded in standard Logo anyway.