SECTION 19 1 ADVISING The operation of advising gives the user a way of modifying a function without necessarily knowing how the function works or even what it does. Advising consists of modifying the interface between functions as opposed to modifying the function definition itself, as in editing. break, trace, and breakdown, are examples of the use of this technique: they each modify user functions by placing relevant computations between the function and the rest of the programming environment. The principal advantage of advising, aside from its convenience, is that it allows the user to treat functions, his or someone else's, as "black boxes," and to modify them without concern for their contents or details of operations. For example, the user could modify sysout to set sysdate to the time and date of creation by advise[SYSOUT;(SETQ SYSDATE (DATE))] As with break, advising works equally well on compiled and interpreted functions. Similarly, it is possible to effect a modification which only operates when a function is called from some other specified function, i.e., to modify the interface between two particular functions, instead of the interface between one function and the rest of the world. This latter feature is especially useful for changing the internal workings of a system function. For example, suppose the user wanted time (Section 21) to print the results of his measurements to the file FOO instead of the teletype. He could accomplish this by ADVISE(((PRIN1 PRINT SPACES) IN TIME) BEFORE (SETQQ U FOO)) Note that advising prin1, print, or spaces directly would have affected all calls to these very frequently used function, whereas advising ((PRIN1 PRINT SPACES) IN TIME) affects just those calls to prin1, print, and spaces from time. Advice can also be specified to operate after a function has been evaluated. The value of the body of the original function can be obtained from the variable !value, as with break1. For example, suppose the user wanted to perform some computation following each sysin, e.g., check whether his files were up to date. He could then: ------------------------------------------------------------------------ 1 Advising was developed and implemented by W. Teitelman. 19.1 2 ADVISE(SYSOUT AFTER (COND ((LISTP !VALUE) --))) 19.1 Implementation of Advising The structure of a function after it has been modified several times by advise is given in the following diagram: Figure 19-1 ------------------------------------------------------------------------ 2 After the sysin, the system will be as it was when the sysout was performed, hence the advice must be to sysout, not sysin. See Section 14 for complete discussion of sysout/sysin. 19.2 The corresponding INTERLISP definition is: (LAMBDA arguments (PROG (!VALUE) (SETQ !VALUE (PROG NIL advice1 . . ADVICE . BEFORE advicen (RETURN body))) advice1 . . ADVICE . AFTER advicem (RETURN !VALUE))) 3 4 where body is equivalent to the original definition. Note that the structure of a function modified by advise allows a piece of advice to bypass the original definition by using the function RETURN. For example, if (COND ((ATOM X) (RETURN Y))) were one of the pieces of advice BEFORE a function, and this function was entered with x atomic, y would be returned as the value of the inner PROG, !value would be set to y, and control passed to the advice, if any, to be executed AFTER the function. If this same piece of advice appeared AFTER the function, y would be returned as the value of the entire advised function. The advice (COND ((ATOM X) (SETQ !VALUE Y))) AFTER the function would have a similar effect, but the rest of the advice AFTER the function would still be executed. ------------------------------------------------------------------------ 3 Actually, advise uses its own versions of PROG, SETQ, and RETURN, (called ADV-PROG, ADV-SETQ, and ADV-RETURN) in order to enable advising these functions. 4 If fn was originally an EXPR, body is the body of the definition, otherwise a form using a gensym which is defined with the original definition. 19.3 19.2 Advise Functions Advise Advise is a function of four arguments: fn, when, where, and what. fn is the function to be modified by advising, what is the modification, or piece of advice. when is either BEFORE, AFTER, or AROUND, and indicates whether the advice is to operate BEFORE, AFTER, or AROUND the body of the function definition. where specifies exactly where in the list of advice the new advice is to be placed, e.g., FIRST, or (BEFORE PRINT) meaning before the advice containing print, or (AFTER 3) meaning after the third piece of advice, or even (: TTY:). If where is specified, advise first checks to see if it is one of LAST, BOTTOM, END, FIRST, or TOP, and operates accordingly. Otherwise, it constructs an appropriate edit command and calls the editor to insert the advice at the corresponding location. Both when and where are optional arguments, in the sense that they can be omitted in the call to advise. In other words, advise can be thought of as a function of two arguments [fn;what], or a function of three arguments: [fn;when;what], or a function of four arguments: [fn;when;where;what]. Note that the advice is always the last argument. If when=NIL, BEFORE is used. If where=NIL, LAST is used. advise[fn;when;where;what] fn is the function to be advised, when=BEFORE, AFTER, or AROUND, where specifies where in the advice list the advice is to be inserted, and what is the piece of advice. If fn is of the form (fn1 IN fn2), fn1 is changed to fn1-IN-fn2 throughout fn2, as with break, and then fn1-IN-fn2 is used in place of 5 fn. If fn is broken, it is unbroken before advising. If fn is not defined, an error is generated, NOT A FUNCTION. If fn is being advised for the first time, i.e., if getp[name,ADVISED]=NIL, a gensym is generated and stored on the property list of fn under the property ADVISED, and the gensym is defined with the original definition of fn. An appropriate ------------------------------------------------------------------------ 5 If fn1 and/or fn2 are lists, they are distributed as shown in the example on page 19.1. 19.4 6 S-expression definition is then created for fn. Finally, fn is added to the (front of) 7 advisedfns. If fn has been advised before, it is moved to the front of advisedfns. If when=BEFORE or AFTER, the advice is inserted in fn's definition either BEFORE or AFTER the original body of the function. Within that context, its position is determined by where. If where=LAST, BOTTOM, END, or NIL, the advice is added following all other advice, if any. If where=FIRST or TOP, the advice is inserted as the first piece of advice. Otherwise, where is treated as a command for the editor, a la breakin, e.g., (BEFORE 3), (AFTER PRINT) . If when=AROUND, the body is substituted for * in the advice, and the result becomes the new body, e.g., advise[FOO;AROUND;(RESETFORM (OUTPUT T) *)]. Note that if several pieces of AROUND advice are specified, earlier ones will be embedded inside later ones. The value of where is ignored. Finally list[when;where;what] is added (by addprop) to the value of property ADVICE on the 8 property list fn. Note that this property value is a list of the advice in order of calls to advise, not necessarily in order of appearance of the advice in the definition of fn. The value of advise is fn. If fn is non-atomic, every function in fn is advised with the same values (but copies) for when, where, and what. In this case, the value of advise is a list of individual functions. Note: advised functions can be broken. (However if a function is broken ------------------------------------------------------------------------ 6 Using private versions of PROG, SETQ, and RETURN, so that these functions can also be advised. 7 So that unadvise[T] always unadvises the last function advised. See page 19.6. 8 So that a record of all the changes is available for subsequent use in readvising, see page 19.6. 19.5 at the time it is advised, it is first unbroken.) Similarly, advised functions can be edited, including their advice. unadvise will still restore the function to its unadvised state, but any changes to the body of the definition will survive. Since the advice stored on the property list is the same structure as the advice inserted in the function, editing of advice can be performed on either the function's definition or its property list. unadvise[x] is a no-spread NLAMBDA a la unbreak. It takes an indefinite number of functions and restores them to their original unadvised state, including removing the properties added by 9 advise. unadvise saves on the list advinfolst enough information to allow restoring a function to its advised state using readvise. advinfolst and readvise thus correspond to brkinfolst and rebreak. unadvise[] unadvises all functions on 10 advisedfns. It first sets advinfolst to NIL. unadvise[T] unadvises the first function of advisedfns, i.e., the most recently advised function. readvise[x] is a no-spread NLAMBDA a la rebreak for restoring a function to its advised state without having to specify all the advise information. For each function on x, readvise retrieves the advise information either from the property READVICE for that function, or from advinfolst, and performs the corresponding advise operation(s). In addition it stores this information on the property READVICE if not already there. If no information is found for a particular function, value is (fn - NO ADVICE SAVED). readvise[] readvises everything on advinfolst. readvise[T] readvises just the first function on advinfolst, i.e., the function most recently unadvised. ------------------------------------------------------------------------ 9 Except if a function also contains the property READVICE (see readvise below), unadvise moves the current value of the property ADVICE to READVICE. 10 In reverse order, so that the most recently advised function is unadvised last. 19.6 A difference between advise, unadvise, and readvise versus break, unbreak, and rebreak, is that if a function is not rebroken between successive unbreak[]'s, its break information is forgotten. However, once readvised, a function's advice is permanently saved on its property list (under READVICE); subsequent calls to unadvise will not remove it. In fact, calls to unadvise update the property READVICE with the current value of the property ADVICE, so that the sequence readvise, advise, unadvise causes the augmented advice to become permanent. Note that the sequence readvise, advise, readvise removes the "intermediate advice" by restoring the function to its earlier state. advisedump[x;flg] Used by prettydef when given a command of the form (ADVISE --) or (ADVICE --). flg=T corresponds to (ADVISE --), i.e., advisedump writes both a deflist and a readvise. flg=NIL corresponds to (ADVICE --), i.e., only the deflist is written. In either case, advisedump copies the advise information to the property READVICE, thereby making it "permanent" as described above. 19.7