{Begin SubSec Advising} {Title Advising} {Text {note Advising was developed and implemented by W. Teitelman.} {Tag Advising} {index *BEGIN* 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 {it interface} between functions as opposed to modifying the function definition itself, as in editing. {fn BREAK}, {fn TRACE}, and {fn BREAKDOWN}, are examples of the use of this technique: they each modify user functions by placing relevant computations {it 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 {fn SYSOUT} to set {lisp SYSDATE} to the time and date of creation by {lisp (ADVISE 'SYSOUT '(SETQ SYSDATE (DATE)))}. As with {fn 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 {it internal} workings of a system function. For example, suppose the user wanted {fn TIME} ({PageRef Fn TIME}) to print the results of his measurements to the file {lisp FOO} instead of the teletype. He could accomplish this by {lisp (ADVISE '((PRIN1 PRINT SPACES) IN TIME) 'BEFORE '(SETQQ U FOO))} Note that advising {fn PRIN1}, {fn PRINT}, or {fn SPACES} directly would have affected all calls to these very frequently used function, whereas advising {lisp ((PRIN1 PRINT SPACES) IN TIME)} affects just those calls to {fn PRIN1}, {fn PRINT}, and {fn SPACES} from {fn TIME}. Advice{index *PRIMARY* 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 {index !VALUE VAR}{var !VALUE}, as with {fn BREAK1}. For example, suppose the user wanted to perform some computation following each {fn SYSIN}, e.g., check whether his files were up to date. He could then{index AFTER (as argument to ADVISE)}: {lisp (ADVISE 'SYSOUT 'AFTER '(COND ((LISTP !VALUE) --)))}.{foot After the {fn SYSIN}, the system will be as it was when the {fn SYSOUT} was performed, hence the advice must be to {fn SYSOUT}, not {fn SYSIN}. See {PageRef Fn SYSOUT} for complete discussion of {fn SYSOUT}. }{comment endfootnote} {Begin SubSec Implementation of Advising} {Title Implementation of Advising} {Text After a function has been modified several times by {fn ADVISE}, it will look like: {lispcode (LAMBDA arguments (PROG (!VALUE) (SETQ !VALUE (PROG NIL advice1 . . {it advice before} . advicen (RETURN {arg BODY}))) advice1 . . {it advice after} . advicem (RETURN !VALUE)))} where {arg BODY} is equivalent to the original definition.{foot If {arg FN} was originally an {lisp EXPR}, {arg BODY} is the body of the definition, otherwise a form using a {index GENSYM FN}{fn GENSYM} which is defined with the original definition. }{comment endfootnote} Note that the structure of a function modified by {index ADVISE FN}{fn ADVISE} allows a piece of {index advice}advice to bypass the original definition by using the function {lisp RETURN}. For example, if {lisp (COND ((ATOM X) (RETURN Y)))} were one of the pieces of advice {index BEFORE (as argument to ADVISE)}{lisp BEFORE} a function, and this function was entered with {lisp X} atomic, {lisp Y} would be returned as the value of the inner {fn PROG}, {var !VALUE}{index !VALUE VAR} would be set to {lisp Y}, and control passed to the advice, if any, to be executed {index AFTER (as argument to ADVISE)}{lisp AFTER} the function. If this same piece of advice appeared {lisp AFTER} the function, {lisp Y} would be returned as the value of the entire advised function. The advice {lisp (COND ((ATOM X) (SETQ !VALUE Y)))} {lisp AFTER} the function would have a similar effect, but the rest of the advice {lisp AFTER} the function would still be executed. Note: Actually, {fn ADVISE} uses its own versions of {fn PROG}, {fn SETQ}, and {fn RETURN}, (called {index ADV-PROG FN}{fn ADV-PROG}, {index ADV-SETQ FN}{fn ADV-SETQ}, and {index ADV-RETURN FN}{fn ADV-RETURN}) in order to enable advising these functions. }{End SubSec Implementation of Advising} {Begin SubSec Advise Functions} {Title Advise Functions} {Text {index *BEGIN* ADVISE FN} {fn ADVISE} is a function of four arguments: {arg FN}, {arg WHEN}, {arg WHERE}, and {arg WHAT}. {arg FN} is the function to be modified by advising, {arg WHAT} is the modification, or piece of advice. {arg WHEN} is either {index *PRIMARY* BEFORE (as argument to ADVISE)}{lisp BEFORE}, {index *PRIMARY* AFTER (as argument to ADVISE)}{lisp AFTER}, or {index *PRIMARY* AROUND (as argument to ADVISE)}{lisp AROUND}, and indicates whether the advice is to operate {lisp BEFORE}, {lisp AFTER}, or {lisp AROUND} the body of the function definition. {arg WHERE} specifies exactly where in the list of advice the new advice is to be placed, e.g., {index FIRST (as argument to ADVISE)}{lisp FIRST}, or {lisp (BEFORE PRINT)} meaning before the advice containing {fn PRINT}, or {lisp (AFTER 3)} meaning after the third piece of advice, or even {lisp (: TTY:)}. If {arg WHERE} is specified, {fn ADVISE} first checks to see if it is one of {index LAST (as argument to ADVISE)}{lisp LAST}, {index BOTTOM (as argument to ADVISE)}{lisp BOTTOM}, {lisp END},{index END (as argument to ADVISE)} {lisp FIRST},{index FIRST (as argument to ADVISE)} or {lisp TOP},{index TOP (as argument to ADVISE)} and operates accordingly. Otherwise, it constructs an appropriate edit command and calls the editor to insert the advice at the corresponding location. Both {arg WHEN} and {arg WHERE} are optional arguments, in the sense that they can be omitted in the call to {fn ADVISE}. In other words, {fn ADVISE} can be thought of as a function of two arguments {lisp (ADVISE {arg FN} {arg WHAT})}, or a function of three arguments: {lisp (ADVISE {arg FN} {arg WHEN} {arg WHAT})}, or a function of four arguments: {lisp (ADVISE {arg FN} {arg WHEN} {arg WHERE} {arg WHAT})}. Note that the advice is always the {it last} argument. If {arg WHEN}={lisp NIL}, {lisp BEFORE} is used. If {arg WHERE}={lisp NIL}, {lisp LAST} is used. {note what a crock!!!} {FnDef {FnName ADVISE} {FnArgs FN WHEN WHERE WHAT} {Text {arg FN} is the function to be advised, {arg WHEN}={lisp BEFORE}{index BEFORE (as argument to ADVISE)}, {index *PRIMARY* AFTER (as argument to ADVISE)}{lisp AFTER}, or {index AROUND (as argument to ADVISE)}{lisp AROUND}, {arg WHERE} specifies where in the advice list the advice is to be inserted, and {arg WHAT} is the piece of advice. If {arg FN} is of the form {lisp ({arg FN1} IN {arg FN2})}, {arg FN1} is changed to {lisp {arg FN1}-IN-{arg FN2}} throughout {arg FN2}, as with break, and then {lisp {arg FN1}-IN-{arg FN2}} is used in place of {arg FN}. If {arg FN1} and/or {arg FN2} are lists, they are distributed as with {fn BREAK0}, {PageRef Fn BREAK0}. If {arg FN} is broken, it is unbroken before advising.{index UNBROKEN (Printed by ADVISE)} If {arg FN} is not defined, an error is generated, {lisp NOT A FUNCTION}.{index NOT A FUNCTION Error} If {arg FN} is being advised for the first time, i.e., if {lisp (GETP {arg FN} 'ADVISED)}={lisp NIL}, a {index GENSYM FN}{fn GENSYM} is generated and stored on the property list of {arg FN} under the property {index *PRIMARY* ADVISED Prop}{prop ADVISED}, and the {fn GENSYM} is defined with the original definition of {arg FN}. An appropriate S-expression definition is then created for {arg FN}.{foot Using private versions of {fn PROG}, {fn SETQ}, and {fn RETURN}, so that these functions can also{index ADV-PROG FN}{index ADV-SETQ FN}{index ADV-RETURN FN} be advised. }{comment endfootnote} Finally, {arg FN} is added to the (front of) {index ADVISEDFNS Var}{var ADVISEDFNS}, so that {index UNADVISE FN}{lisp (UNADVISE T)} always unadvises the last function advised (see {PageRef Fn UNADVISE}). If {arg FN} has been advised before, it is moved to the front of {var ADVISEDFNS}. If {arg WHEN}={lisp BEFORE} or {lisp AFTER}, the advice is inserted in {arg FN}'s definition either {index BEFORE (as argument to ADVISE)}{lisp BEFORE} or {index AFTER (as argument to ADVISE)}{lisp AFTER} the original body of the function. Within that context, its position is determined by {arg WHERE}. If {arg WHERE}={lisp LAST},{index LAST (as argument to ADVISE)} {index BOTTOM (as argument to ADVISE)}{lisp BOTTOM}, {lisp END}, or {lisp NIL}, the advice is added following all other advice, if any. If {arg WHERE}={lisp FIRST}{index FIRST (as argument to ADVISE)} or {index TOP (as argument to ADVISE)}{lisp TOP}, the advice is inserted as the first piece of advice. Otherwise, {arg WHERE} is treated as a command for the editor, similar to {fn BREAKIN}, e.g., {lisp (BEFORE 3)}, {lisp (AFTER PRINT)}. If {arg WHEN}={lisp AROUND},{index AROUND (as argument to ADVISE)} the body is substituted for {lisp *} in the advice, and the result becomes the new body, e.g., {lisp (ADVISE 'FOO 'AROUND '(RESETFORM (OUTPUT T) *))}. Note that if several pieces of {lisp AROUND} advice are specified, earlier ones will be embedded inside later ones. The value of {arg WHERE} is ignored. Finally {lisp (LIST {arg WHEN} {arg WHERE} {arg WHAT})} is added (by {fn ADDPROP}) to the value of property {index ADVICE Prop}{prop ADVICE} on the property list of {arg FN}, so that a record of all the changes is available for subsequent use in readvising. Note that this property value is a list of the advice in order of calls to {fn ADVISE}, not necessarily in order of appearance of the advice in the definition of {arg FN}. The value of {fn ADVISE} is {arg FN}. If {arg FN} is non-atomic, every function in {arg FN} is advised with the same values (but copies) for {arg WHEN}, {arg WHERE}, and {arg WHAT}. In this case, {fn ADVISE} returns a list of individual functions. }} Note: advised functions can be broken. However if a function is broken at the time it is advised, it is first unbroken. Similarly, advised functions can be edited, including their advice. {index UNADVISE FN}{fn 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. {index *END* ADVISE FN} {FnDef {FnName UNADVISE} {FnArgs X} {Type NOSPREAD NLAMBDA} {Text An nlambda nospread like {fn UNBREAK}. It takes an indefinite number of functions and restores them to their original unadvised state, including removing the properties added by {fn ADVISE}. {fn UNADVISE} saves on the list {var ADVINFOLST} enough information to allow restoring a function to its advised state using {index READVISE FN}{fn READVISE}. {var ADVINFOLST} and {fn READVISE} thus correspond to {var BRKINFOLST} and {fn REBREAK}. If a function contains the property {index READVICE Prop}{prop READVICE}, {fn UNADVISE} moves the current value of the property {index ADVICE Prop}{prop ADVICE} to {prop READVICE}. {lisp (UNADVISE)} unadvises all functions on {index ADVISEDFNS Var}{var ADVISEDFNS} in reverse order, so that the most recently advised function is unadvised last. It first sets {index ADVINFOLST Var}{var ADVINFOLST} to {lisp NIL}. {lisp (UNADVISE T)} unadvises the first function of {var ADVISEDFNS}, i.e., the most recently advised function. }} {FnDef {FnName READVISE} {FnArgs X} {Type NOSPREAD NLAMBDA} {Text An nlambda nospread like {fn REBREAK} for restoring a function to its advised state without having to specify all the advise information. For each function on {arg X}, {fn READVISE} retrieves the advise information either from the property {index READVICE Prop}{prop READVICE} for that function, or from {index ADVINFOLST Var}{var ADVINFOLST}, and performs the corresponding advise operation(s). In addition it stores this information on the property {index READVICE Prop}{prop READVICE} if not already there. If no information is found for a particular function, value is {lisp ({arg FN} - NO ADVICE SAVED)}. {lisp (READVISE)} readvises everything on {var ADVINFOLST}. {lisp (READVISE T)} readvises the first function on {var ADVINFOLST}, i.e., the function most recently unadvised. }} A difference between {fn ADVISE}, {fn UNADVISE}, and {fn READVISE} versus {fn BREAK}, {fn UNBREAK}, and {fn REBREAK}, is that if a function is not rebroken between successive {lisp (UNBREAK)}'s, its break information is forgotten. However, once {fn READVISE} is called on a function, that function's advice is permanently saved on its property list (under {prop READVICE}); subsequent calls to {fn UNADVISE} will not remove it. In fact, calls to {index UNADVISE FN}{fn UNADVISE} update the property {prop READVICE} with the current value of the property {index ADVICE Prop}{prop ADVICE}, so that the sequence {index READVISE FN}{fn READVISE}, {fn ADVISE}, {index UNADVISE FN}{fn UNADVISE} causes the augmented advice to become permanent. Note that the sequence {fn READVISE}, {fn ADVISE}, {fn READVISE} removes the "intermediate advice" by restoring the function to its earlier state. {FnDef {FnName ADVISEDUMP} {FnArgs X FLG} {Text Used by {index PRETTYDEF FN}{fn PRETTYDEF} when given a command of the form {lisp (ADVISE {ellipsis})}{index ADVISE FileCom} or {lisp (ADVICE {ellipsis})}.{index ADVICE FileCom} If {arg FLG}={lisp T}, {fn ADVISEDUMP} writes both a {fn DEFLIST} and a {index READVISE FN}{fn READVISE} (this corresponds to {lisp (ADVISE {ellipsis})}). If {arg FLG}={lisp NIL}, only the {fn DEFLIST} is written (this corresponds to {lisp (ADVICE {ellipsis})}). In either case, {fn ADVISEDUMP} copies the advise information to the property {index READVICE Prop}{prop READVICE}, thereby making it "permanent" as described above. }} }{End SubSec Advise Functions} {Begin Note} Date: 14 Apr 1982 1732-PST From: Dave Dyer Subject: argument names I've discovered a widespread source of incompatability among our implementations; that the argument names of functions are frequently different. Normally this doesn't cause proplems, but but ADVICE implicitly depends on the names of the arguments of the function being advised. A cursory survey reveals over 100 cases of incompatability. what to do?: (1) punt, but try to keep the names the same for documented functions. (2) add syntax to ADVICE to specify the arguemnt names in effect when the advice was created, allowing warnings and/or automatic conversion if the names differ. (3) add syntax to ADVICE to specify args implictly, eg. $$ARG1 I favor 1 for expediency and 3 for the long term. I've always been annoyed to have to do (ARGLIST mumble) before advising anyway. Side note: ADVICE actually uses SMARTARGLIST, which sounds wrong to me, since smartarglist isn't well publicized, and since it doesn't necessarily relate to the names bound by the function advised. Date: 14 APR 1982 2142-PST From: MASINTER at PARC-MAXC Subject: ADVICE and ARGNAMES To: DDyer at ISIB I believe we've been very careful to preserve argument names in Interlisp-D to be in accordance with the "SMARTARGLIST" names precisely because of ADVISE. In any case, ADVISE uses SMARTARGLIST, so that users can save the ARGNAMES property and be guaranteed that the advice will take independently of the implementation. The ADVISE/ADVICE file package commands save the ARGNAMES property as well if it is present. Date: 15 Apr 1982 1001-EST Sender: YONKE at BBNG Subject: Re: argument names Interlisp-Jericho has also tried to be careful in preserving argument names. When we find that we screwed-up somehow we correct it immediately. I suggest that this course of action is the correct one to take for the time being. (Larry, didn't someone at PARC write a program to compare argument names between Interlisp-10 and Interlisp-D some time ago? We never used it, but it may be handy if such a program existed.) Date: 15 Apr 1982 1827-PST From: Dave Dyer Subject: argnames I ran a comparison of SMARTARGLIST between the vax and pdp-10 and between our dolphins and the pdp-10, the results are VAX: 251 different argument lists DOLPHIN: 283 differen argument lists ... I think we both have a problem. Date: 15 Apr 1982 1849-PST Sender: LYNCH at USC-ISIB Subject: Re: argnames Well, if the PDP-10 version has problems then I suggest it is not worth fixing unless we get an extended address version of Interlisp for the PDP-10. Poor old PDP-10 (KA days) is just too small an address space for modern Interlisp to be constrained by. (That's my viewpoint: anyone still think the PDP-10 should be carried along "as is"?) Date: 16 Apr 1982 1126-EST Sender: YONKE at BBNG Subject: re: argnames One suggestion for finding the "truth" is to build it by creating a loadable file which puts the ARGNAMES property on every "general Interlisp function" (i.e. everything but the low-level machine-dependent functions). Then implementations of Interlisp could write programs to automatically compare the "real" argument names vs. the "true" argument names. Once we had this "gospel" it would be up to the implementors to either load this file in their loadups or actually go in and change the argument names in the function definitions to match the gospel. Of course, like all gospels they have to be revised every once in a while, but this should cut down on the religious arguements. {End Note} {index *END* advising} }{End SubSec Advising}