Yazoo ---> Online Help Docs ---> Yazoo scripting ---> Functions

Code substitution

Up until this point, running a function has involved some bit of code working hand-in-hand with the function's own private variables, and perhaps also a few variables stored in the parent class or in the global space. What we will describe in this section is an operator that allows a function to invade some other object and do its dirty work there, reading and modifying that object's members rather than its own. The operator that accomplishes this is called the code substitution operator, and it has the symbol `<<'.

First we'll prove to ourselves that a substituted function truly has all the power of native code to read, write, create and destroy. Start off with a self-contained little function that writes its contents to the screen, and then quietly self-immolates by removing its only member.


    goodbye :: {
       my_data := "Goodbye..."
       
       code
       
       myself := @this
       print(myself[1])
       remove myself[1]
    }
   

One code substitution is enough to change goodbye() from suicidal to homicidal:


    process_data :: {
       names[10] :: ...
       ...
    }
   
    (process_data << goodbye)()
   

As we can see, the code to run is the right-hand argument of the code-substitution operator, and the object that it will run inside is the left argument. Notice that, after substituting the code, we still had to put the usual parentheses to tell it to run. Nothing else changes: as in an ordinary function call only the three lines following the code statement of goodbye are executed inside process_data.

The substitution of code itself is quite temporary -- the code stays substituted only until the end of that expression. On the other hand, whatever effect the substituted code has on its host when run is quite as permanent as anything native code can do. In our last example, the process_data function will be right back to its crusted old self next time we try to run it in the normal way, except with the major difference that it will be missing its names array.

Substituting inlined code can often save quite a bit of typing when some obscure place in memory needs prolonged use. For example, if we want to initialize a parameter list that has some awkward path, we could either write:


    database.addresses.WriteAddress.params.include_country = true
    database.addresses.WriteAddress.params.justification = "left"
    ...
   

or else, just:


    (database.addresses.WriteAddress.params << {
       code
       
       include_country = true
       justification = "left"
       ...
    })()
   

Very importantly, we had to write a code marker into the substituted code -- when we run the substituted function only what follows the code marker will executed. The constructor of the substituted code (the part before the code marker) is not used at all for code substitution in the present version of Yazoo.

One thing that we will point out here, and delve into more thoroughly in the next section, is that a function substituted into some object has access to all of the global members that that function, not the object, normally sees. In this context, by `global' we refer to any member outside of the immediate function; it could have been defined in the enclosing package, or all the way at the top. Code substitution only affects the first stop on the function's `search path', which is now object being substituted into rather than the function's own storage space. The rest of the search path still runs through the variables enclosing the function, even though that function is not presently running in that region of memory. The upshot is that the substituted code in our last example could reference variables that we had defined just prior to the substitution by the same script, but it could not access, say, any of WriteAddress's variables aside from params.

As advertised in the last section, code-substitution allows us to incorporate optional arguments into functions in an elegant way. To do this, we have to group all of the optional parameters into some composite variable. When the function runs we first read in the required parameters as usual, then initialize our optional parameters variable, and finally run the arguments, as a function, inside of that variable. That way, by writing commands after the code marker or semicolon of the function's arguments the default parameters can be changed in the same way that any other variable can be modified. Here is an example.


    WriteAddress :: {
       params :: {
           include_country :: Boolean
           justification :: string   }
       
       name :: street_address :: string
       ...
       
       code
       
       name = args[1]
       ...
       
       params = { false, "left" }
       (params << args)()
       ...
    }
   

A typical call to WriteAddress() might look like


    jst := "center"
    WriteAddress("Bob Jones", "65 Maple Ave", ... ; justification = jst)
   

Optional parameters are less obscure than the mandatory ones since their variables are named explicitly, and they can be named in any order and omitted when the defaults are suitable. Again, even though the args code was substituted into params, it retained its original search path, so that it could access, for example, the jst variable, though not the members of WriteAddress().

One thing that will not work is to put the optional arguments into a set, unless the members of the set are explicitly named. For example, had we written


       include_country :: Boolean
       justification :: string
       
       params :: { include_country, justification }
       ...
    }
   

the arguments to WriteAddress() would not access either of these parameters by name. The first element of the set points to include_country, but that params[1] has no name. The section on sets explains how to manually assign names to the set elements if we want to use this method.

Functions can have more than one coding block, and unsurprisingly function arguments can too. This can be useful if we want to run different function arguments in different spaces, or incorporate dependencies between different arguments. Here is an example that does both.


    f :: {
       ...
       params_1 :: { save_log := false }
       params_2 :: { filename = "log.txt" }
       
       (params_1 << args)()
       if params_1.save_log == true
           (params_2 << args#2)()
       endif
       
       ...
    }
   
    f( ; save_log = true; filename = "tmp")
   

As we mentioned earlier, the so-called `workspace' of Yazoo's interactive command prompt is not the real workspace where the start.zoo script runs. Instead, start.zoo creates this comfortable illusion inside of a fake workspace variable, furnished with the routines in user.zoo and within which the user's commands are run, all by substituting code into this variable. This gives is a third use for code-substitution: to create a little bubble-world where any number of functions can run and share data in the same playground. Actually, if we want true peace of mind that the substituted functions will not wreak havoc with the rest of the program, we sometimes also have to clip the functions' search paths, and we shall explain how to do that in the next section.


Prev: Function arguments   Next: Search Paths and Fibrils


Last update: July 28, 2013

Get Yazoo scripting language at SourceForge.net. Fast, secure and Free Open Source software downloads