Scilab Bag Of Tricks: The Scilab-2.5.x IAQ (Infrequently Asked Questions) | ||
---|---|---|
Prev | Chapter 4. Unknown Spots | Next |
Scilab bears a feature which strongly reminds one of object-oriented programming languages: operator overloading. Yet, Scilab is not object oriented. Strictly speaking operator overloading has nothing to do with object-oriented programming, but as it turns out overlading operators is particularly useful in object-oriented languages. Overloading grafts new functionality onto an existing function-name. Scilab accomplishes this with a special syntax similar to mangled symbol names of a C++-compiler's output.
The overloading of operators is described in Section 3.3 ("Definition of Operations On New Data Types") of in SCI/doc/Intro.ps, and information is also available through help overloading.
Even if you never will overload an operator, knowing the syntax and the function codes helps when deciphering error messages that involve overloaded operators. The following session transcript shows what happens if you request the boolean matrix bm to be converted into a string.
-->bm = [%t, %f; %f, %f] bm = ! T F ! ! F F ! -->string(bm) !--error 246 impossible to overload this function for given argument type(s) undefined function %b_string
Without further knowledge the user is nothing but puzzled by "undefined function %b_string".
If we drop the buzzword "Operator overloading", which comes from the OO-camp, and call every operator a function, we are (a) absolutely right in a mathematical sense, and (b) get a good grasp of what is going on. Operators are simply functions written in a special syntax. In most imperative languages (C and descendants, Pascal and descendants) functions are written in prefix notation, i.e. the function name precedes all arguments. In the same languages operators are written in infix notation, i.e. the operator put between the operands.
Lisp as a (functional) language which employs pure prefix syntax all functions and operators are written before all arguments.
(+ 3.9 54.0 -4.5 74.5 -57 -56) (setq x (list "a" -1 (- 3 10))) (length x)
The same expressions look more or less differently in Scilab:
3.9 + 54.0 + (-4.5) + 74.5 + (-57) + (-56) sum( [3.9 54.0 -4.5 74.5 -57 -56] ) -- alternative to previous line x = list("a", -1, (3 - 10)) length(x)
As becomes clear in the above example, operators are specially written functions, but otherwise behave like ordinary functions.
Overloading has been hyped since to advent of C++. A closer look reveals that even Fortran-77 endows certain intrinsics with an overloaded syntax. What the heck is overloading? To overload a symbol means assigning another meaning to it, augmenting the existing meaning(s). Typically, the symbol is a function name and the additional meaning is an additional function definition.
How can the language decide which definition to take? That depends on the language. The most common scheme to determine which function definition to trigger is the analysis of the actual function arguments. Fortran-77 provides so-called generic functions, sin is one example, which can be called with arguments of several types and the compiler selects the routine that matches that type.
program f77ovl implicit none real xr, s1 double precision xd, s2 complex xc, s3 * floating point literals default to real*4 in f77 xr = 1.0 s1 = sin(xr) -- compiler selects single precision routine xd = 1.0d0 s2 = sin(xd) -- compiler selects double precision routine xc = (1.0, 0.0) s3 = sin(xc) -- compiler selects complex routine * alternative using explicit call s2 = dsin(xd) -- user demands double precision routine s3 = csin(xc) -- user demands complex routine end
Modern languages like e.g. C++, F9x, and Ada let the user define functions with the same name as long as they can be uniquely identified by their argument list (C++) or argument list and return value (Ada).
We can define three Maximum functions. The Ada-compiler distinguishes them by their arguments and return values.
function Maximum(X1, X2 : Float) return Float; function Maximum(F1, F2 : Fraction) return Fraction; function Maximum(I1, I2 : ArbitraryPrecisionInteger) return ArbitraryPrecisionInteger;
As we know from the beginning of this section, operators are functions written in a special way. Thus it is easy to imagine that an operator can be overloaded just the way a function can. The next Ada example shows how the addition operator can be overloaded.
function "+"(Left, Right : Fraction) return Fraction is begin return -- code for addition of two fractions end "+";
If you want to learn more about overloading and class construction and object oriented (C-) programming, we recommend Scott Myers' books [Myers:EffCPP:1998] , and [Myers:MoreEffCPP:1996] .
In Scilab an operator gets overloaded with a new function, if we define this function having a special name in a particular format. For unary operators the format is
function result = %optype_opcode(argument)
whereas for binary operators except insertion and extraction it is
function result = %optype1_opcode_optype2(argument1, argument2)
where valid operand-variable-type codes for optype, optype1, and optype2 are defined in Table 4-3 and Table 4-4, and the operator-codes opcode are defined in Table 4-5. The formal function arguments argument, argument1, argument2, and result are usual argument and return-value names. To descibe the syntax in words: a percent-sign starts the definition followed by the type[s] of the operands and the operator seperated by [an] underscore[s].
The syntax for overlading vector/matrix insertion
target(index1, index2, ..., indexN) = source
and vector/matrix extraction
target = source(index1, index2, ..., indexN)
is a bit more convoluted as it has to account for the indices:
// insertion function target = %targettype_i_sourcetype(index1, index2, ..., indexN, source, target) // extraction function [result1, result2, ..., resultM] = %sourcetype_e(index1, index2, ..., indexN, source)
![]() |
The online-help of Scilab-2.5, help overloading, is incorrect in its explanation of the argument names to insertion-overloading. It says that target is the next-to-last, and source is the last argument. In fact the two arguments occupy exchanged positions as we have listed them. |
Note that for extraction the number of return values, M, is completely independent of the number of index expressions, N.
Table 4-3. List of all operand type codes
Variable type | Code string optype | Code index |
---|---|---|
floating point scalar, vector, or matrix | s [a] | 1 |
polynomial | p[a] | 2 |
boolean | b[a] | 4 |
sparse matrix | sp | 5 |
sparse boolean matrix | spb | 6 |
Matlab® sparse matrix | msp | 7 |
matrix of integers (8, 16, or 32bit entries) | i | 8 |
string | c[a] | 10 |
uncompiled function | m[a] | 11 |
compiled function | mc | 13 |
function library | f | 14 |
untyped list | l[a] | 15 |
typed list | name of the tlist [b] | 16 |
matrix list | ml | 17 |
pointer | ptr | 128 |
? | ip | 129 |
Notes: a. This type code is already overloaded by Scilab itself. b. The formal code string for typed lists is tl. |
Two types are particularly well suited for overloading; these are the tlist and its close relative the mlist. tlists are used by Scilab itself to define some sophisticated types like polynomials, or sparse boolean matrices. Table 4-4 summarizes all types t, in use as of version 2.5.1. As the type of a tlist is the list's first element, we sometimes call it, in a Lisp like manner, the head of the tlist. Note that when working with tlists of type t, Scilab calls the predefined function for untyped lists, %l_opcode, or %l_opcode_l until the user provides [a] replacement function[s] with the name %t_opcode, or %t_opcode_t.
![]() |
Only the first 8 characters of the name of a tlist or mlist are significant when overloading any unary or binary operator! See also Section 4.6.1. |
The first column of Table 4-4 states the name of the variable type, column two lists the tlist identification heads, and column three holds the code number, Scilab associates with the specific head. The type-name/type-code – not only for tlists – translation can be queried with the typename function.
Table 4-4. List heads used by Scilab
Variable type | Code string | Code index |
---|---|---|
sparse matrix | sp | 5 |
sparse boolean matrix | spb | 6 |
Matlab® sparse matrix | msp | 7 |
linear-state system | lss | 16 |
rational function, i.e. quotient of two polynomials | r | 16 |
hyper-matrix | hm | 17 |
? | ip | 129 |
Now that we have defined all operand type codes, we can turn to the operator type codes.
![]() |
Several operators listed in Table 4-5 behave specially! Some, like equality and inequality tests, are auto-overloaded, i.e. are available even before the user defines her replacement function. Others cannot be overloaded at all, like unary plus (all types), and insertion/extraction (tlists). |
![]() |
There is a mistake in the SCI/doc/Intro.ps concerning this operator. The first table in Sec. 3.3, page 64 states that code b defines the row-separator ";". SCI/doc/Intro.ps is wrong here, but the online help is correct in that the row-seperator ";" is overloaded with the code f. |
![]() |
The online-help of Scilab-2.5, help overloading, is incorrect, and SCI/doc/Intro.ps is correct. u is associated with "*.", and x with ".*". |
Table 4-5. Operator type codes
Operator | Code | Note |
---|---|---|
.' | 0 | pure transposition (no complex conjugate) |
< | 1 | less-than |
> | 2 | greater-than |
<= | 3 | less-or-equal |
>= | 4 | greater-or-equal |
~ | 5 | logical-not |
+ | a | binary operator. The unary plus is automatically overloaded for any new type with the identity-transformation or "do nothing". Unary plus cannot be overloaded! |
: | b | range generator |
[ , ] | c | matrix row constructor "," |
./ | d | element-wise division |
( ) | e | extraction form a matrix, like s = v(k). The operator is automatically defined for new types. Extraction from tlists cannot be overloaded, use mlists instead. |
[ ; ] | f | concatenation or matrix column construction ";" |
| | g | logical-or |
& | h | logical-and |
( ) | i | insertion into a matrix, like v(k) = s. The operator is automatically defined for new types. Insertion into tlists cannot be overloaded, use mlists instead. |
.^ | j | element-wise exponentiation |
.*. | k | Kronecker multiplication |
\ | l | left division; solve a linear system of equations |
* | m | matrix multiplication |
<>, ~= | n | unequality test. Both operators are automatically defined for any new type with list semantics, i.e. component-wise comparison and a boolean return vector. Both can be overloaded with user functions. |
== | o | equality test. The operator is automatically defined for any new type with list semantics, i.e. component-wise comparison and a boolean return vector. It can be overloaded with a user function. |
disp | p | unary operator; display results with disp or at the command line |
^ | p | binary operator; matrix exponentiation |
.\ | q | element-wise left division |
/ | r | right matrix division |
- | s | unary %head_s, and binary %head1_s_head2 operator; see also: overloading of unary plus %head_a. |
' | t | unary operator, Hermitian (complex) transposition |
*. | u | element-wise multiplication |
/. | v | element-wise division |
\. | w | element-wise right division |
.* | x | element-wise multiplication |
./. | y | Kronecker division |
.\. | z | Kronecker right division |
Almost all unary built-in functions like abs, ceil, floor, imag, int, real, round, sqrt, and string can be overloaded, too. The syntax borrows for the syntax for unary operators. Function names which are not already used by Scilab cannot be used for overloading.
function result = %optype_functioname(argument)
where functioname is the name of the function.
![]() |
Lots of overloading functions can be found in SCI/macros/percent. |
After so much theory, definitions and tables we deserve an example that demonstrates operator overloading in action. As usual for sci-BOT the complete example can be found in Section 10.1. The following is not production strength code, most error checks are left out.
function f = frac(p, q, reduce) // constructor for fractions select type(p) case 1 then // constant if size(p, '*') ~= 1 then error('argument p is non-scalar') end p0 = p q0 = 1 case 16 then // tlist // copy constructor behavior p0 = p('num') q0 = p('denom') else error('argument p has wrong type') end if isdef('q') then // q is an optional argument select type(q) case 1 then // constant if size(q, '*') ~= 1 then error('argument q is non-scalar') end q0 = q0 * q case 16 then // tlist // copy constructor behavior p0 = p0 * q('denom') q0 = q0 * q('num') else error('argument q has wrong type') end end if isdef('reduce') then // (isdef('reduce') & reduce == %t) does not work, for // Scilab performs a complete boolean evaluation if reduce == %t then [p_red, q_red] = reduce_int(p0, q0) else p_red = p0 q_red = q0 end else [p_red, q_red] = reduce_int(p0, q0) -- reduce_int defined in complete example end f = tlist(['frac'; 'num'; 'denom'], p_red, q_red) function s = %frac_p(f) // display function for fractions s = string(f) disp(s) // // addition // function r = %frac_a_frac(f1, f2) d1 = gcd_int(f1('denom'), f2('denom')) if d1 == 1 then r = frac(f1('num')*f2('denom') + f1('denom')*f2('num'), .. f1('denom')*f2('denom')) else t = f1('num')*(f2('denom') / d1) + f2('num')*(f1('denom') / d1) d2 = gcd_int(t, d1) r = frac(t/d2, (f1('denom') / d1)*(f2('denom') / d2)) end // // conversion // function fl = frac2float(f) // convert a fraction to a floating point number fl = f('num') / f('denom') function s = %frac_string(f) // string( frac(...) ) if f('denom') == 1 then s = sprintf('%.0f', f('num')) else s = sprintf('%.0f/%.0f', f('num'), f('denom')) end
After loading these definitions a new type named frac exists. It can be used like this:
f = frac(2, 3); g = frac(1, 3); h = frac(-1, 3); i = frac(12); f + g g + h i frac2float(h)
[1] |
Thanks go to Bruno Pinçon for carefully proofreading this section. |