Parameter Passing Top ConcurrencyOverriding Functions Contents

Overriding Functions

Suppose one wishes to count how many additions are performed when code in a module is executed. One way to do this is to override the built-in addtion function:

    (define plus-count 0)
    (define (+ a b)
        (assign plus-count (+ plus-count 1))
        (+ a b)

The problem here is that the original binding of + is lost when the new version is defined. Calling + now will result in an infinite recursive loop.

One solution is to save the original binding of + before the new version is defined:

    (define old+ +)
    (define (+ a b)
        (assign plus-count (old+ plus-count 1))
        (old+ a b)

With the original version of + bound to old+, now a and b can be added together properly.

Scam automates this process with two functions, redefine and prior. If the new version of a function is "redefined" rather than defined, the previous binding of the function is saved in the function closure that results. The prior function is then used to retrieve this binding. Here is a rewrite of the above code using these functions:

    (include "reflection.lib")

    (redefine (+ a b)
        (assign plus-count ((prior) plus-count 1))
        ((prior) a b)

The redefine and prior functions can be accessed by including reflection.lib.

Implementing redefine and prior

Recall that closures are objects in Scam. The redefine function works by adding a field to the closure object generated by a function definition. It begins by delaying evaluation of the paramater list and the function body and then extracting the function name from the parameter list.

    (define (redefine # $params $)
        ;obtain the function name
        (define f-name (car $params))
        ;find the previous binding of the function name
        ;if no prior binding, use the variadic identity function 
        (if (defined? f-name #)
            (define f-prior (get f-name #))
            (define f-prior (lambda (@) @))
        ;now generate the function closure
        (define f (eval (cons 'define (cons $params $)) #))
        ;add the previous binding to the closure
        (addSymbol '__prior f-prior f)

It continues by looking up the function name in the calling scope, #, binding that value to the symbol f-prior. If no binding exists, an identity function is bound to f-prior. Next, the desired function definition is processed by building the code for a function definition from the delayed parameter list and the delayed body. Finally, a new field is added to the function closure and bound to the prior function.

The prior function then looks for the added symbol and returns its binding. It does so by extracting the constructor of the calling environment and then retrieving the value of the symbol that was added by redefine:

    (define (prior #)
        (define f (# '__constructor))
        (f '__prior)

Cloning functions

If you override a function defined in an enclosing scope, only calls to the overridden function in the current scope see the new definition. Calls made in the enclosing scope to the overridden function see the old version. A scope violation would occur otherwise. To solve this problem, one can override the offending function or more simply, clone it. Cloning a function creates a new definition in the current scope. The only difference is the definition environment is changed to the current environment, the function parameter list and the body remain unchanged.

To clone a function, one calls the clone function, passing in the function to be cloned. Consider this code:

    (include "reflection.lib")

    (define (f x) x)
    (inspect this)
    (inspect (f '__context))

        (inspect (local? 'f this))
        (define f (clone f))
        (inspect (local? 'f this))
        (inspect this)
        (inspect (f '__context))

The output generated will be something like:

    this is <object 11698>
    (f '__context) is <object 11698>
    (local? (quote f) this) is #f
    (local? (quote f) this) is #t
    this is <object 13317>
    (f '__context) is <object 13317>

The first two calls to inspect show that f's definition environment is the outer scope. The next call to inspect shows that f is not defined in the inner scope. The last three calls show that f is now defined with the proper definition environment.

For an example of using clone, see the chapter on Objects.

Parameter Passing Top ConcurrencyOverriding Functions Contents