Prev: about-this-doc.html Up Next: program-invocation.html
Design concepts

BLOP is written in C++. Its user interface is CINT (a C/C++ interpreter). So the syntax of the user's scripts is C++, in which all the classes and functions provided by the BLOP package are usable. Why have I chosen CINT?

Object-oriented design is a great help in software developement and maintenance. However, when the whole software is 'exported' through a C++ interpreter interface to the user, this can make its daily use awkward and difficult, and gets in conflict with the main goal, which is to provide an easy-to-use scripting language. For small, often changing tasks you don't want to type thousands of characters, writing a well-structured, object-oriented program. BLOP was therefore designed to satisfy these needs.

It's of no doubt, that a detailed documentation (which is easy to navigate) is necessary for any software. But a good design does not only mean, that everything is well structured and well documented: it must reflect the human way of thinking, and strict conventions must be followed throughout the whole software, such that if the user gets familiar with these concepts, he does not have to browse the documentation for a new command or solution, but he can figure it out from his previous experiences and from these concepts. This is why I attempt to summarize these concepts. And there is another reason: this software most probably is far from completely matching these concepts. This summary should also set an ideal niveau, to which I will hopefully frequently be constrained by the feedback of users.

Ok, so much about philosophy. Let me tell some examples (which are not necesserily existing in the software...!) If you find yourself having trouble with frequently typing linetype(...) instead of the existing linestyle(...) function,

In this section you will most probably find expressions, which are not self-explanatory. Read this section without worrying about that, and maybe return later, after getting familiar with these.

Member access, chaining
Data members in object oriented programming are most frequently hidden from the user (they are made private), and access is provided via member functions instead. This has several good reasons, which are not mentioned here. There are different conventions how these functions are named. One would be for example (for obtaining and setting the minimum of an axis' range) double get_min() and void set_min(double). Since the arguments of such simple functions (no argument in the first case, one argument in the second) clearly show, what the function is going to do, the following conventions are followed in blop: the function name is the same for member access and member modification functions, and
  • member access functions have no argument, and return (not too surprisingly) the value of the data member in question
  • member modification functions take 1 argument (the new value of the data member), and return a reference to the class itself, making it possible to chain several such function calls in one statement:
    axis.min(1.2).max(4.5).title("axis title");
Wrapper functions
The object-orientedness, and the object hierarchy in blop are (hopefully) clear, and logic. For example, setting the range of the xaxis of the current frame, one should write:
Clear and nice, isn't it? But who the hell wants to have always a clear and nice, but so long script just to set the axis range, or to do some other minor tasks? Nobody could convince a user that he should write 100s of characters for the easiest task.

Therefore there are wrapper functions, which are intuitive, easy to use, and: SHORT. They are implemented as static member function of the set class, to have a syntax which mimics gnuplots syntax. Here only a few examples are provided, refer to the header file set.h

void set::x1range(double min, double max);  // set range of x1 axis
void set::x1title(const var &);             // set axis title
Object creation+administration: mknew, xdraw
Objects during a normal run of blop are organised in a relatively strict hierarchy. This hierarchy must be built up (some objects have to be added into others [containers], and so on). If this would be done automatically by the constructors, the objects could not be handled separately from other objects. Therefore the constructors do not make this administration, they only create the objects. Instead, the classes provide static member functions, which not only create a new object, but also put it into the hierarchy, and make all administration (see below). There are 3 categories:
  • Container-type objects (pad, frame, mframe, legendbox), provide the static mknew(...) functions.
  • Other objects provide the fdraw, pdraw and cdraw static member functions, which create a new object, and add it into the current frame, pad or canvas, respectively.
  • Graphs are created with the plot(...) or mplot(...) functions (which read a datafile, create a graph and add it to the current frame).
These functions of course differ in their arguments, but they have the following common properties:
  • They create a new object dynamically (using operator new)
  • Place it automatically into the object hierarchy
  • Set the autodel flag of the object to true (meaning that garbage collection is automatically done for these objects, you don't have to take care of deleting them; whenever a container is cleared or destroyed, all objects contained in this container and having autodel==true will be automatically deleted)
  • They have no more arguments than absolutely necessary. (This of course depends on the type of the object being created, and my and your ideas about what is necessary might differ. An example: to draw an arrow, you have to specify the start and end points, it has no sense to just draw an arrow without these specifications. Its color, linewidth, etc are, however, not absolutely necessary. So these properties can be set calling member functions on the object, using chaining, see next point)
  • These functions return a reference to the newly created object. This makes it possible to call member functions to set object properties in the same statement. For example to create an 2x2 mpad and set its properties, say:
    mpad &p = mpad::mknew(2,2).gap(1*MM).fillcolor(red).bordercolor(blue);
Without these functions one should type a lot:
   arrow *a = new arrow;
   a->autodel = true;
   a->from(MM, CM);
   a->to(3*CM, 5*CM);
Using these functions, however, life is easier:
Pointer or reference? (. or -> ?)
  • The above mentioned mknew, current and xdraw functions return references to objects. The reason of this is the following: member modification functions (as discussed earlier) return references to the objects, in order that they can be chained. At the time of creation of an object for example with the mknew function one can immediately set properties using these member modification functions, using the . operator everywhere (and not -> after mknew, and . later). Moreover, if one wants to store a reference to the object, the same syntax can be used regardless if properties are set:
       pad &p = pad::mknew(0,0,0.5,0.5); ,or
       pad &p = pad::mknew(0,0,0.5,0.5).bordercolor(blue).borderwidth(MM);
    Otherwise, if mknew returned a pointer, one should write:
       pad *p = pad::mknew(0,0,0.5,0.5);
       pad &p = pad::mknew(0,0,0.5,0.5)->bordercolor(blue).borderwidth(MM);
    Returning a reference also indicates, that these functions (in principle :-) always succeed (returning a 0-pointer could indicate failure).
  • Member functions of classes, which access other objects within that class (for example the axis of a frame), return pointers. This also separates your command:
    (if there would be a . instead of the ->, the whold statement could be seen by a superficial reader as setting many properties of frame::current())
Default properties
Many graphical objects have properties (like pointcolor, linewidth, etc of graphs, textcolor of text labels, etc etc etc). These properties can be accessed or changed using member functions (see above). However, one often does not like the default values, and would like to switch to other default values so that he does not have to set these properties for each individual object. In order to allow this, blop provides an easy way to change the default values: each object (... in the future :-) has static member functions to set the default values. The name of these static member functions follows the following convention: to set the default value for a property called xyzw, there exists a member function (see above) with the same name xyzw to set or get this property. The default value can be set via the static
function, which has the same argument. All object created after calling this function will be initialized with the new default value. Example:
   plot("datafile").ds(points());   // this file is plotted with blue points
Option classes
If a function had more than 2 arguments, I could never in my life remember the order, in which these arguments had to be provided to the function. These arguments often specify different setting, fine-tuning of the layout of objects, etc, and one usually does not want to bother about setting most of them, but rely on defaut values. There can be of course default values for function parameters in C++, but this does not solve the order-problem just described, and if you want to set for example only the last one, you will have to write out all the preceeding parameters as well.

In the case of plotting or drawing objects, the guideline was (see mknew, fdraw, etc) that the functions creating/drawing an object only take the absolutely necessary parameters, create an object, attach it to the current pad, container or canvas, and return a reference to this object. Any further property of them (such as orientation, color, fontsize, etc) can be set on the returned reference. This could be done there, because all these object properties were not used at the time of the function call, but only later, when your picture is printed to a terminal.

In many other cases this can not be done. For example when fitting or histogramming, one needs to specify many options to a C-function, which will be used at that moment. In order to avoid the many-argument-problem, in these cases the option classes will be used (for example fitopt for fitting, or histopt for histogramming. These classes are simply the collection of a lot of properties, parameters, or whatever you call them. They have corresponding member access functions with a self-describing name (your code will be more readable), and these member access functions return references to the object itself. Therefore, they can be chained, as described above. Moreover, they will have the default_xxx functions as well, to set the default values of these properties. This way you can very cleanly set default arguments to these functions, which take option classes as arguments.

For example consider the fitting procedure (concentrate only on the last argument):
fit(Graph, Model, fitopt().verbose(2).x(_1,_2).y(_3));
The statement fitopt() creates a (temporary) object of type fitopt, on which you call several member-setting functions. If you want to make a lot of calls to the function fit, with the same options, you can set the default values, and then you don't have to provide them each time:

fit(Graph, Model);
Symbolic constants
Symbolic constants are put into the namespace sym in order to avoid global namespace pollution. Examples: sym::dashed, sym::solid (linestyles), or sym::left, sym::center, etc for positioning. See the source file sym.h. If somebody wants to avoid the usage of the sym:: namespace resolution in front of these constants, he can specify 'using namespace sym;' to bring these constants into the global namespace;

Prev: about-this-doc.html Up Next: program-invocation.html