"Standard" Euphoria manipulates data structures such as
object
s, sequence
s, atom
s,
integer
s and any other type the user chooses to adopt
or define. It does the manipulation by means of routines, some
written in the program, others being in libraries.
Authors might be RDS, the user or, perhaps, other users.
EuCANOOP offers an exactly analogous form of processing, but with just one data structure - the Object - and, at this stage anyway, just two pre-written routines. The difference is that the variables and routines are stored within the data structure and manipulation takes place solely within the Object.
You start by defining Classes, as prototypes of Objects.
After each definition is complete the details of that Class
are stored for future use. Once you have declared a Class you
can create Instances of it (Objects), setting values specific
to the Instance. Manipulation of an Object is possible because
it carries within its values not only its "variables" but also
pointers to routines (both procedures
and
functions
) which operate on the Object's variables.
This is all achieved using two standard Euphoria constructs:
sequence
routine_id
The rest is what I once heard called "syntactical sugar" - coding to provide stylistic simplification. This is what the EuCANOOP library provides.
Read on if you wish for more detail now, or jump to the library's specification.
You define a Class using the procedure Class
.
It takes one parameter:
string
containing the name you wish to associate
with that Class.
Class("Dog")If you wish the Class to inherit from another pre-defined Class then use the procedure
Extends
.
Multiple inheritance is supported.
Extends
takes one parameter:
array
of one or more string
s
containing the name(s) of the Class(es).
Extends({"Animal"})
If you wish to add property values then you use the
procedure Property
. It takes three parameters:
string
containing the name you wish to associate
with the property.
integer
signifying the type of the variable
(ATOM for atom
, SEQUENCE for sequence
, etc).
object
containing the default value for the property.
This value should be of the correct type. If it isn't then a basic default
is set instead.
My convention is to use upper-case text for property names.
Property("FEET",INTEGER,0)
If you wish to add a method then use the procedure Method
.
It takes two parameters:
string
containing the name you wish to associate
with the method.
integer
carrying the return value from
routine_id
.
The parameter for that call should be the name of the routine you wish
to invoke when the method is called
(see later for how to code methods and how to invoke them).
The routine is either pre-defined, outside the Class definition, or
may be included within it.
Method("Bark",routine_id("Dog_Bark"))Repeat Property and/or Method calls as required to define all the values you need.
If you wish to assign an inherited Method to a different routine then
you use the procedure Override
.
Just like Method
it takes two parameters:
string
containing the name of the inherited method.
integer
carrying the return value from
routine_id
for the "new" routine.
A Class definition is only stored when the procedure
EndClass
is called. It takes one parameter:
string
containing the name you have already used to
define the Class.
EndClass("Dog")I find it useful to use an indenting convention for Class declaration. For example:
Class("Dog") Extends({"Animal"}) Property("BREED",SEQUENCE,"") Method("Bark",routine_id("Dog_Bark")) EndClass("Dog")EuCANOOP's Objects are operational instances of Class paradigms. Objects are created (in the image of their Class definition) using the function
New
. It takes two parameters:
string
containing the name of the Class from which
the Object derives.
array
of one or more array
s.
Each inner array contains two elements:
string
containing the name of the property whose
value you wish to set.
object
containing the value you wish to assign
to that property.
(If the value is not of the correct type then the default setting for the
class is substituted.)
Fido = New("Dog",{{"BREED","Mongrel"}})All Objects are created with two standard Methods:
Get
and Set
.
These Methods can be used to inspect or change the values of properties.
Methods are called as follows:
If the Method relates to a procedure
then you use
MethodProc
. It takes three parameters:
Instance
] name of the Object.
string
containing the name of the Method.
sequence
containing the parameters required by the
procedure associated with the method - excluding the first (see later).
MethodProc(StdOut,"Puts",{"Hello, World!"})
If the Method relates to a function
then you use
MethodFunc
. It takes three parameters:
Instance
] name of the Object.
string
containing the name of the Method.
sequence
containing the parameters required by the
procedure associated with the method - excluding the first (see later).
if MethodFunc(StdIn,"GetC",{}) then end ifRoutines (qua Methods) must be defined as follows:
The routine must have at least one parameter. This is an
Instance
, which is used to refer to the Object itself.
The remaining parameters, if any, are the ones to be called by the Method.
To avoid ambiguity my convention is to name routines in a consistent
fashion. For example, if I intend to define a method called "What",
for Class "Fred", with two method parameters (excluding "self") then I
would name the routine Fred_What_2
.
Example:
procedure Output_Puts_1(Instance self,string text) puts(self,text) end procedure
New
is insufficient.
Moreover, some users with other OOP language experience actually want to
define Constructors. You can easily write conventional constructors by
combining a call to New
with a relevant creator
routine. So, for example:
constant myFile = New("File",{{"NAME","Hello.ex"},{"MODE","r"}}) MethodProc(myFile,"Open",{})could be replaced by
constant myFile = File("Hello.ex","r")if the constructor
File
is defined as follows:
global function File(string name,string mode) Instance obj integer handle obj = New("File",{{"NAME",name},{"MODE",mode}}) handle = open(name,mode) MethodProc(obj,"Set",{"HANDLE",handle}) return obj end functionMy convention is to use, as the creator function, a function with precisely the same name as the Class.
Instance
, which enables
the programmer to distinguish all his/her Object instances.
This version extends to range of possible types - both for general programming use and in defining EuCANOOP properties. The additional types offered are:
array
- a sequence where the types of all elements are
mutually compatible
bool
- TRUE or FALSE
byte
- a value in the range 0-255
character
- a value in the range 0-127
string
- an array of bytes
array_of_strings
- an array in which all elements are strings
Just like Euphoria's variables, properties of Objects are typed. EuCANOOP, however, uses its own type-checking routines, so property definitions use EuCANOOP's own types. In addition to the base types defined in Version 2 (OBJECT, ATOM, INTEGER and SEQUENCE) this version offers the addditional EuCANOOP types: ARRAY, BOOL, BYTE, CHARACTER, STRING and STRING_ARRAY.
Type-checking in EuCANOOP takes place when a Class Property's default is
set, when an Instance
is created with a non-default parameter
and when the built-in "Set" Method is invoked. Unlike Euphoria, a type error
does not result in program termination. Although an error message is issued
if an assignment fails, the property's value is set, respectively, to a
"null" value for that type, the Class default or is left unchanged.
Just as in standard Euphoria it is easy to define your own EuCANOOP types to add to the list above. To find out how see here.
object
a)
- checks that a
is an array
object
a)
- checks that a
is an atom
object
bool)
- checks that bool
is a bool
object
byt)
- checks that byt
is a byte
(0 to 255)
object
char)
- checks that char
is a character
(0 to 127)
object
a)
- checks that a
is an Instance
object
a)
- checks that a
is an integer
object
a)
- checks that a
is an object
object
a)
- checks that a
is a sequence
object
str)
- checks that str
is a string
object
str)
- checks that str
is an array
of
string
s
array
- a one-dimensional array of compatible type (all atom
,
all integer
or all sequence
)
integer
function doesn't distinguish between x and x.0!)
bool
- TRUE or FALSE
byte
- 0 to 255
character
- 0 to 127
Instance
- for instantiation of Objects
string
- a one-dimensional array of characters
including the empty string {}
string_array
- a one-dimensional array of strings
string
name)
- opens the class definition
string_array
parents)
- inherits from parent(s)
string
property,
integer
typ,object
default) -
defines a new property, names it, declares its type, and specifies
a default value
string
method,integer
rid)
- defines a new method, names it and indicates the routine-id to which it
refers
string
method,
integer
rid) -
defines the routine-id to which an existing method should now refer
string
name)
- completes the class definition
Instance
self,string
method,sequence
args)
Instance
self,string
method,sequence
args)
string
class,sequence
args)
- creates an Instance of type class, with the parameter to
initialise properties.
The args should be sequences containing pairs:
string
class)
- details the contents of the Class
Instance
obj)
- details the current contents
of the Object