[Contents] [TitleIndex] [WordIndex

Emulate Object Oriented in Scilab

Abstract

The current version of the Scilab language is procedural. The goal of this page is to explore ways to emulate OO in the Scilab language.

Introduction

When the Scilab language was designed in the end 70s, the only programming model available was the procedural model. Now that the Object Oriented languages are widespread, it is natural to explore the OO methodology in Scilab.

Designing an OO system for Scilab would be highly desirable, and will certainly be done in the coming years. This is so true that Yann Collette and I independently worked on the same subject at the same time. The SEP #7 is dedicated to OO in Scilab :

http://viewvc.scilab.org/bin/cgi/viewvc.cgi/trunk/SEP/SEP_7_object.odt?view=log

In this document, we make the hypothesis that this goal is beyond the scope of this page. Instead, we present a simple method to emulate OO with current Scilab features. The suggested method is classical when one want to emulate OO in a non-OO language, for example in fortran (see the references below). It will be easy to extend when the Scilab v6 release will be available, perhaps providing namespaces.

Example

The suggested method may allow to simplify many Scilab primitives, which are based on optionnal arguments. For example, the "optim" primitive has 20 arguments, some of them being optionnal :

[f [,xopt [,gradopt [,work]]]]=optim(costf [,<contr>],x0 [,algo] [,df0 [,mem]] [,work] [,<stop>] [,<params>] [,imp=iflag])

with <params>, for example, being a list of optional 4 arguments :

'ti', valti ,'td', valtd

Scilab users and developers may want to add new features to the "optim" primitive, but that may lead to many difficulties :

If an OO system was available for Scilab, the management of the arguments would be solved with less difficulty (that will be detailed later). The more important problem of managing the layers of the software would be solved by providing a hierarchy of components :

The interface of the "optimoo" primitive may be the following. A new optimoo object would be created with the "optimoo_create" command :

optimoo_create()

Now, we can start to configure that static object, from the number of unknown to the solver required by the user. This example shows that the number of options of the optimization solvers is unlimited. If the user does not provide an explicit parameter, the default value is used instead. Here we exemplify how the list of 20 positionnal arguments are replaced by calls to the "optimoo_configure" method.

optimoo_configure("-nx",5)
optimoo_configure("-nbequality",2)
optimoo_configure("-nbinequality",5)
optimoo_configure("-costf",mycostfunction)
optimoo_configure("-solver",interiorpoint)
optimoo_configure("-linesearchinterpolation","quadratic")
optimoo_configure("-linesearchstop","Wolfe")
optimoo_configure("-maxiter",500)
optimoo_configure("-verbose",1)

The optimization component may start by a search for an initial guess, may be a random method based on 1 000 trials. The following lines shows that the number of methods associated with the optimization component may be extended.

optimoo_configure("-initguessmethod","Sobol")
optimoo_configure("-initguessnbshots",1000)
x0= optimoo_searchx0()

This is a major advantage on the previous approach, where the "optim" primitive only offered one method.

The search for the optimum may be now performed with a call to the "optimoo_solve" method.

optimoo_configure("-initguess",x0)
optimoo_solve()

The internal structures are now updated, but we do not have the optimum value. The results can be retrieved with the "optimoo_get" method. The list of 4 output arguments are replaced by a list of calls to the "optimoo_get" method.

xstar=optimoo_get("-optimumparameter")
fstar=optimoo_get("-optimumcost")
gstar=optimoo_get("-optimumgradient")

The previous example shows that the development process of OO models is scalable, that is, the complexity of the system is (at best) a linear function of the number of features.

The page is structured as follows. In the first section, we give some details about why classical OO extension systems cannot be used with the Scilab language. In the next section, we give a simple approach to model a static Person class. In the final sectin, we discuss other technics which could be explored.

Why the classical approach fails

The classical method to extend a non-object language into an OO framework is

The abstract data structure to emulate OO depends on the language

The "new" constructor is emulated by returning an instance of the ADT. The "new" method may require to allocate memory. The "free" destructor takes "this" as first argument and frees the memory which have have been allocated previously. This approach is possible in fortran, C, and other compiled languages, if the first argument "this" can be modified by the method. In C, this is done by passing a pointer to the ADT. In fortran 90, this is done by adding "intent(inout)" to the declaration (or nothing at all).

In the Scilab language, the data structure of choice is the "tlist". In the following example, we define a person by its name, first name, phone number and email.

p1 = tlist(["T_DB_PERSON","name","firstname","phone","email"])

The constructor may then be designed so that it returns a new tlist.

function newperson = person_new ()
        newperson = tlist(["T_PERSON","name","firstname","phone","email"])
        newperson.name=""
        newperson.firstname=""
        newperson.phone=""
        newperson.email=""
endfunction

The next step in the emulation is to design methods which takes "this" as first argument. For example, the "person_configure" would modify its "this" argument.

function person_configure (this,key,value)
        select key
        case "-name" then
                this.name = value
        case "-firstname" then
                this.firstname = value
        case "-phone" then
                this.phone = value
        case "-email" then
                this.email = value
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction

This approach is appealing, but fails and here is the reason. In the Scilab language, input arguments, in the right hand side, are separated from the output arguments, in the left hand side. The most important feature is that input argument cannot be modified. If an input argument is modified, only the local copy is modifed, not the variable in the caller's context.

If you execute the following script,

p1= person_new ()
person_configure (p1,"-name","Bob")
p1

you will see that the local value of "p1" is as before, with empty fields.

Comment: Unless the local p1 was passed at the caller level using resume. However, the function person_configure should know what was p1's name at caller level -- see request 139...

Anwser: The solution may indeed be based on a combination of execstr and resume. See below for a complete try of this method.

This is why this method based on emulation does not work in Scilab.

In this document, we try to explore other ways to achieve OO in the Scilab language :

In the following section, the "singleton" approach is explored : we show how a OO approach allows to define one instance of a person. This design is called a "Singleton" in Design Patterns vocabulary [10].

A Singleton Person class

This example is chosen because it is a classical example in the OO literature.

The method consists in implementing a static instance of a person. Here, "static" means that there is only one instance of the object, that is, only one Person. Before getting into the details on how to do it, let's detail the resulting interface.

exec person.sci;
person_new();
person_configure("-name","Backus")
person_configure("-firstname","John")
person_configure("-phone","01.42.53.34.85")
person_configure("-email","john.backus@ibm.com")
person_display()
name = person_cget("-name")
person_free()
person_new();
person_configure("-name","Torvalds")
person_configure("-firstname","Linus")
person_configure("-phone","02.37.98.19.33")
person_configure("-email","linux.torvalds@sun.com")
person_display()
person_free()

One immediately recognise the previous "optimoo" class, which shows that this example may be too simple, but may have practical value. The immediate drawback is that only one person can be managed at a time, hence the term "static".

This is done by maintaining a global variable which is hidden to the client code. The Scilab data structure is a tlist.

The constructor, named "person_new", creates a tlist and store it in a global variable.

function person_new ()
        global DB_PERSON
        DB_PERSON = tlist(["T_DB_PERSON","name","firstname","phone","email"])
        DB_PERSON.name=""
        DB_PERSON.firstname=""
        DB_PERSON.phone=""
        DB_PERSON.email=""
endfunction

The destructor simply deletes the global variable.

function person_free ()
        global DB_PERSON
        DB_PERSON = null()
endfunction

Configuring and accessing to the fields is based on the Scilab "select case" construct.

function person_configure (key,value)
        global DB_PERSON
        select key
        case "-name" then
                DB_PERSON.name = value
        case "-firstname" then
                DB_PERSON.firstname = value
        case "-phone" then
                DB_PERSON.phone = value
        case "-email" then
                DB_PERSON.email = value
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction
function value = person_cget (key)
        global DB_PERSON
        select key
        case "-name" then
                value = DB_PERSON.name
        case "-firstname" then
                value = DB_PERSON.firstname
        case "-phone" then
                value = DB_PERSON.phone
        case "-email" then
                value = DB_PERSON.email
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction

A "person_display" static method is based on the "mprintf" statement.

function person_display ()
        global DB_PERSON
        mprintf("Person\n")
        mprintf("Name: %s\n", DB_PERSON.name)
        mprintf("First name: %s\n", DB_PERSON.firstname)
        mprintf("Phone: %s\n", DB_PERSON.phone)
        mprintf("E-mail: %s\n", DB_PERSON.email)
endfunction

Putting all the pieces together leads to the following script.

function person_new ()
        global DB_PERSON
        DB_PERSON = tlist(["T_DB_PERSON","name","firstname","phone","email"])
        DB_PERSON.name=""
        DB_PERSON.firstname=""
        DB_PERSON.phone=""
        DB_PERSON.email=""
endfunction
function person_free ()
        global DB_PERSON
        DB_PERSON = null()
endfunction
function person_display ()
        global DB_PERSON
        mprintf("Person\n")
        mprintf("Name: %s\n", DB_PERSON.name)
        mprintf("First name: %s\n", DB_PERSON.firstname)
        mprintf("Phone: %s\n", DB_PERSON.phone)
        mprintf("E-mail: %s\n", DB_PERSON.email)
endfunction
function person_configure (key,value)
        global DB_PERSON
        select key
        case "-name" then
                DB_PERSON.name = value
        case "-firstname" then
                DB_PERSON.firstname = value
        case "-phone" then
                DB_PERSON.phone = value
        case "-email" then
                DB_PERSON.email = value
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction
function value = person_cget (key)
        global DB_PERSON
        select key
        case "-name" then
                value = DB_PERSON.name
        case "-firstname" then
                value = DB_PERSON.firstname
        case "-phone" then
                value = DB_PERSON.phone
        case "-email" then
                value = DB_PERSON.email
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction

In the next section, we try to design a class so that several objects can be managed at the same time.

A "basic" person Class

The main limitation of current Scilab input / output arguments is that one argument cannot be both input and ouput. One simple workaround for this problem is to state that one input argument is also an output argument. For example, the "person_configure" method can be modified in the following way :

function this = person_configure (this,key,value)
        select key
        case "-name" then
                this.name = value
        case "-firstname" then
                this.firstname = value
        case "-phone" then
                this.phone = value
        case "-email" then
                this.email = value
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction

In this case, the input argument "this" is also declared to be an output argument. To use the "person_configure" method, one must then set "this" both as an output argument and as an input argument :

p1=person_configure(p1,"-name","Backus")

Other methods of the class are to be modified to follow that model. Putting all together leads to the following Scilab script.

function newperson = person_new ()
        newperson = tlist(["T_DB_PERSON","name","firstname","phone","email"])
        newperson.name=""
        newperson.firstname=""
        newperson.phone=""
        newperson.email=""
endfunction
function person_free (this)
        // TODO ...
endfunction
function person_display (this)
        mprintf("Person\n")
        mprintf("Name: %s\n", this.name)
        mprintf("First name: %s\n", this.firstname)
        mprintf("Phone: %s\n", this.phone)
        mprintf("E-mail: %s\n", this.email)
endfunction
function this = person_configure (this,key,value)
        select key
        case "-name" then
                this.name = value
        case "-firstname" then
                this.firstname = value
        case "-phone" then
                this.phone = value
        case "-email" then
                this.email = value
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction
function value = person_cget (this,key)
        select key
        case "-name" then
                value = this.name
        case "-firstname" then
                value = this.firstname
        case "-phone" then
                value = this.phone
        case "-email" then
                value = this.email
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
endfunction

The following test script shows how to use that class.

exec person.sci;
p1 = person_new();
p1=person_configure(p1,"-name","Backus")
p1=person_configure(p1,"-firstname","John")
p1=person_configure(p1,"-phone","01.42.53.34.85")
p1=person_configure(p1,"-email","john.backus@ibm.com")
person_display(p1)
name = person_cget(p1,"-name")
person_free(p1)

As we see, the "basic" is not very elegant in the sense that p1 is to be specified twice when we want to configure the object. The main benefit of this approach is that we can manage several objects at the same time : recall that the "singleton" approach was based on only one instance of Person.

The "basic" method has the drawback of requiring that the object needs to be specified both as an input argument and as an output argument. In the next section, one tries to solve the problem by passing only the name of the variable to the methods.

With a string handle

An "handle" is classicaly used as a way to specify an object, without passing the object explicitely. This can be achieved with the Scilab language, in which we can execute strings as commands with the "execstr" command.

The "string handle" method is based on the creation of one global variable "p1", which name is passed to the methods as in the following script.

global p1
person_new("p1");
person_configure("p1","-name","Backus")
person_configure("p1","-firstname","John")
person_configure("p1","-phone","01.42.53.34.85")
person_configure("p1","-email","john.backus@ibm.com")
person_display("p1")
name = person_cget("p1","-name")
person_free("p1")

The "p1" handle is managed by the constructor by creating a local variable "newperson". The "global" statement is then used to define the "p1" variable as a global variable. This global variable is then set to the value of the local variable "newperson", so that the variable in the caller's context is modified as expected.

function person_new (this)
        newperson = tlist(["T_DB_PERSON","name","firstname","phone","email"])
        newperson.name=""
        newperson.firstname=""
        newperson.phone=""
        newperson.email=""
        execstr("global "+this)
        execstr(this+" =newperson")
endfunction

The previous script may look complicated. In fact, the script acts as the following one, which is crystal-clear :

newperson = tlist(["T_DB_PERSON","name","firstname","phone","email"])
...
global p1
p1 = newperson

The "configure" method can be written in the same style. First, one computes the name of the field to be configured. Then one uses "execstr" to update the global variable.

function person_configure (this,key,value)
        select key
        case "-name" then
                field=".name"
        case "-firstname" then
                field=".firstname"
        case "-phone" then
                field=".phone"
        case "-email" then
                field=".email"
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
        execstr("global "+this)
        execstr(this+field+"=''"+string(value)+"''")
endfunction

The previous function is complicated by the handling of quotes, which must be doubled when required inside a string. When the "p1" handle is passed to the configure method along with the "-name" key and value "foo", the execstr commands acts as

global p1
p1.name='foo'

Putting all the pieces all together, the whole class can be written as the following script.

function person_new (this)
        newperson = tlist(["T_DB_PERSON","name","firstname","phone","email"])
        newperson.name=""
        newperson.firstname=""
        newperson.phone=""
        newperson.email=""
        execstr("global "+this)
        execstr(this+" =newperson")
endfunction
function person_free (this)
        // TODO...
endfunction
function person_display (this)
        mprintf("Person\n")
        mprintf("Name: %s\n", person_cget(this,"-name"))
        mprintf("First name: %s\n", person_cget(this,"-firstname"))
        mprintf("Phone: %s\n", person_cget(this,"-phone"))
        mprintf("E-mail: %s\n", person_cget(this,"-email"))
endfunction
function person_configure (this,key,value)
        select key
        case "-name" then
                field=".name"
        case "-firstname" then
                field=".firstname"
        case "-phone" then
                field=".phone"
        case "-email" then
                field=".email"
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
        execstr("global "+this)
        execstr(this+field+"=''"+string(value)+"''")
endfunction
function value = person_cget (this,key)
        select key
        case "-name" then
                field=".name"
        case "-firstname" then
                field=".firstname"
        case "-phone" then
                field=".phone"
        case "-email" then
                field=".email"
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
        execstr("value="+this+field)
endfunction

With resume

This method is based on the combination of execstr and resume. Following this thread

http://groups.google.com/group/comp.soft-sys.math.scilab/browse_thread/thread/35d8791ade6bef22/59299eba39c7a3fe?lnk=gst&q=object#59299eba39c7a3fe where Alexander Vigodner used exestr(name+'=resume(x)')

we tried the method suggested by Alexander Vigodner. The method suppose that the name of the object is passed as an additionnal input argument. Then, at the end of the "person_configure" method, we add a "resume" statement which update the tlist in the caller's context :

execstr(thisname+"=resume(this)")

The full script for "person_configure" then becomes :

function person_configure (this,thisname,key,value)
        select key
        case "-name" then
                this.name=value
        case "-firstname" then
                this.firstname=value
        case "-phone" then
                this.phone=value
        case "-email" then
                this.email=value
        else
                errmsg = sprintf("Unknown key %s",key)
                error(errmsg)
        end
        execstr(thisname+"=resume(this)")
endfunction

This method makes necessary to pass an additionnal "p1" input argument when calling "person_configure" :

person_configure(p1,"p1","-name","Backus")

The method works well, but requires the additionnal argument "p1" which compares equally with the "basic" method (in which p1 is set both as an input argument and an output argument). So there is no syntaxic advantage for this method, but it is more elegant.

Notice that current Scilab release, v5.0.1, does not support namespaces. This is just a hypothesis, to show what would happen if such a feature was available.

Suppose that namespaces are available in Scilab and that the "::" symbol is used to separate the name of the namespace from the name of its content.

The immediate benefit is that the global namespace may contain much less commands and variables. In the current version of Scilab, each primitive is defined in the global namespace : all commands, such as "optim", for example, are defined at the global level. If Scilab features where to be extended, it may be difficult in several releases to find a name which is not allready used by the Scilab language itself.

Considering the problem of emulating OO, the namespace feature would be very convenient. In the following script, one defines the "person" namespace (following Tcl syntax) and one variable with tlist type inside that namespace.

namespace eval person
  variable tlist DB_PERSON
endnamespace

For example, the constructor person_new may be modified as follows. Notice that the "global" statement has disappeared, since DB_PERSON is now a variable associated with the "person" namespace.

function person::new ()
        DB_PERSON = tlist(["T_DB_PERSON","name","firstname","phone","email"])
        DB_PERSON.name=""
        DB_PERSON.firstname=""
        DB_PERSON.phone=""
        DB_PERSON.email=""
endfunction

This feature may help to prevent name collisions : the user may define its own DB_PERSON without interfering with the variable person::DB_PERSON.

The namespace may be used to store directly the fields associated with the person. In the following script, one defines 4 string variables in the "person" namespace.

namespace eval person
  variable string name
  variable string firstname
  variable string phone
  variable string email
endnamespace

The namespace may additionnaly simplify calls from inside the namespace. For example, in the following display method, one uses the "cget" method to retrieve the value associated with each field. Since "display" is defined from the "person" namespace, the cget method is implicitely associated with the full qualified "person::cget" method. This may be done in the Scilab interpreter, at name-resolution time.

function person::display (this)
        mprintf("Person\n")
        mprintf("Name: %s\n", cget("-name"))
        mprintf("First name: %s\n", cget("-firstname")
        mprintf("Phone: %s\n", cget("-phone"))
        mprintf("E-mail: %s\n", cget("-email"))
endfunction

While overloading (compiled) Scilab primitives is possible, overloading macros is not. The following page seems to suggest a solution for that specific issue :

http://www.cert.fr/dcsd/idco/perso/Magni/s_sym/overload.html

Conclusion

In this document, we presented three methods to emulate Object Orientation in Scilab

  • a Singleton model,
  • a "basic" emulated OO system,
  • an emulated OO system based on string handles.

The Singleton model is the most easy to implement, but has the following drawbacks :

  • a name clash may occur against the "hidden" global variable which stored the data,
  • only one instance of the object can be managed at the same time.

The "basic" model is a little more complicated to implement. The main benefit over the "Singleton" method is that several objects can be managed at the same time. The major drawback is that, when the object is to be modified, as in the "configure" method, the object must be specified both as an ouput argument and as an input argument.

The "string handle" method is the most complicated of all three. It has the benefit that, when configuring the object, the object handle appears only once. The main drawback is that the internal management of the string handle is much more complicated than previously, because "execstr" statements are not so easy to deal with. The additional "global" statement which must be written before the object creation is also a drawback.

The current approach may be both over-simplistic and over-complicated. Obviously static instances of the singleton pattern will not solve all problems. But in many situations, this approach may be sufficient and may have practical advantages over the procedural model.

Several features may be added to the Scilab language so that the task of designing a simple OO system would be more easy. The following list is a suggestion for such features :

  • input/output variables, so that a Scilab input variable may be written and stay modified as an output variable,
  • namespaces, so that the "hidden" static variable of the singleton model does not interfere with user's variables.

References

[1] "High-Performance Object Oriented Programming in Fortran 90", http://www.cs.rpi.edu/~szymansk/oof90.html

[2] "Object-based programming in Fortran 90", Mark G. Gray, Randy M. Roberts, 1997 http://www.ccs.lanl.gov/ccs4/pdf/obf90.pdf

[3] "Snit's Not Incr Tcl", William H. Duquette, http://www.wjduquette.com/snit/

[4] "STOOOP", Jean-Luc Fontaine, http://jfontain.free.fr/stooop.html

[5] "Object-Oriented Programming with ANSI C", www.cs.rit.edu/~ats/books/ooc.pdf

[6] "Object Oriented Programming in C", Zack Smith, http://home.comcast.net/~fbui/OOC.html

[7] "Object-oriented programming in C", Paul Field, http://www.accu.informika.ru/acornsig/public/articles/oop_c.html

[8] "Object Oriented C", http://pqnelson.blogspot.com/2007/09/object-oriented-c.html

[9] "ObjectStructure.h", Scilab source file, http://viewvc.scilab.org/bin/cgi/viewvc.cgi/trunk/scilab/modules/graphics/includes/ObjectStructure.h?view=markup

[10] "Singleton Pattern", http://en.wikipedia.org/wiki/Singleton_pattern


2022-09-08 09:27