Skip to content

Custom Types

  1. The Basics
    a. A very basic example
    b. Something slightly more complex
        i. Errors
       ii. Creating data structures using types
  2. Something more powerful
  3. Examples
    a. Example 1 – Drilldown Application
    b. Example 2 – ___ Application
  4. Source Code
    a. Example 1

User-defined types are a significant advantage for any serious programmer, and can even be useful to those less experienced. At their simplest, these may consist of little more than a combination of value types that can be used together as a single unit.

First Some Basic Information

Type definitions must be located outside of any function, but do not need to precede them. In fact, custom type definitions can be located in an included file and not be in the main source code at all.

There are three (technically four) keywords that can be used when creating a custom type: embed, reference, and resolve. By default, properties will be added as references to a specified type (or any type when using type(*), any value using type(=), or any matching tagged type when using type(<tagname>)

To embed a property the embed keyword can be added to the end of a statement. To switch the default from referencing to embedding properties place the embed keyboard as its own line in the type definition. To switch back simply do the same with the reference format. These switches only apply within that type definition

A quick note about embedding objects: There are some objects which cannot be embedded, such as fsfileinputstream, fsfileoutputstream or any of the ppcstype1 objects. However, references to any object type can be part of type definitions.

The last proper keywords is a special one. Normally when properties aren’t embedded they are not examined when trying to resolve the name of a property or method, but if the resolve keyword is added to the end of the property definition, then at runtime the property itself will be included when searching for a property or method that is not listed at the first level of the type definition. This will become clearer later.

A Basic Example

A very basic example of a custom type is a structure containing locale information. This type of structure would need to be passed into any function that is going to format a number, date, time or which converts these from a string representation to a more useful one. Such a structure might look like this:

type tLocaleInfo
  string sDecimalSep embed
  string sThousandsSep embed
  string sListSep embed
end type

This very basic example shows the convenience of custom types, this is generally far more convenient than passing and keeping track of each of these individually.

Something slightly more complex

This example is a gradual introduction into custom types, building into something more complex with functions

type mytype
  string m1
end type

In this basic code m1 contains a reference to a string object.

If we add the embed keyword to the end of this line, however, it will now contain an actual string object, and not just refer to one

type mytype1
  string m1 embed
end type

To use these two types the following little pieces of code will come in handy:

function main()
  mytype m
  string s

  m =@ mytype.new()
  s = "hello"
  m.m1 =@ s
  s = "foo"
end function m.m1

This function will return foo since m.m1 contains a reference to s

function main()
  mytype1 m

  m =@ mytype1.new()
  m.m1 = "hello"
end function m.m1

This will return hello, since m.m1 is an embedded string.

Errors

The following couple examples will all still use mytype and mytype1 but will all return errors

function main()
  mytype m

  m =@ mytype.new()
  m.m1 = "hello"
end function m.m1

This will result in an error since hello is not an object, it is a value and this type can only hold a reference to an object.

type mytype2
  mytype mm1 embed
  mytype mm2
end type

function main()
  mytype2 m
  string s

  m =@ mytype2.new()
  s = "hello"
  m.mm1 =@ mytype.new()
  m.mm1.m1 =@ s
end function m.mm1.m1

This example will return Error 52 Not Embeddable since we have not defined mytype as embeddable. To fix this we have to rewrite mytype

type mytype embed
  string m1
end type

This makes the type itself embeddable.

type mytype embed
  string m1
end type

type mytype2
  mytype mm1 embed
  mytype mm2
end type

function main()
  mytype2 m
  string s

  m =@ mytype2.new()
  s = "hello"
  m.mm1.m1 =@ s
end function m.mm1.m1

Creating data structures using Custom Types

Custom types can also create more complex data structures such as lists and trees in memory. The following is an example of a singly-linked list which should return hello.

type mytype
  string m1
  mytype next
end type

function main()
  string s
  mytype m,mfirst

  s = "hello"
  m =@ mytype.new()
  mfirst =@ m
  m.next =@ mytype.new()
  m =@ m.next
  m.next =@ mytype.new()
  m =@ m.next
  m.next =@ mytype.new()
  m =@ m.next
  m.next =@ mytype.new()
  m =@ m.next
  m.m1 =@ s
  m =@ mfirst
  mfirst =@ .nul

end function m.next.next.next.next.m1

Something more powerful

The previous examples have all consisted of only values and references to values but have not included any custom methods, to make more powerful tools we will now add some methods to these functions.

Any user-defined type can also have a user-defined new() method, this allows the programmer to initialise the newly created object when it is created.

Custom Methods

For those newer to programming, it might be useful to first explain what a method is and how it differentiates subtly from a function. In practical terms there is no difference between the two in SIMPOL, and if you wish to call them functions instead of methods that’s fine. However, the term method is more applicable than function here as methods are defined as part of a (normally internal) class, which is the case here

Now to defining your very own custom method, the syntax for this is very easy, simply add the keyword function followed by the method name to your type declaration (as below).

function copy

As you may have spotted from the above examples every type comes with an inbuilt new() method, this does not need to be listed inside the type definition unless you wish to reinitialise the object after its initial call.

Note: custom methods must be defined in the same module (.sma file) where the type itself is defined and must follow the type definition in the code file.

Creating the method is much like creating any other function, except before the function name you have to add the type and a dot operator

function tCustInfo.copy(tCustInfo me)

As you can see from above the first argument to any method must be the type itself.

Note: in the case of the new() method it must return the object of the type that was passed in, otherwise the assignment to the variable will fail.

If that didn’t make sense maybe the following code can clear things up for you:

type tCustInfo export
  string sCustID embed
  string sFirstname embed
  string sLastname embed
  datetime dtCreated embed
  string sCreatedBy embed
  function copy
end type

function tCustInfo.new(tCustInfo me, string sCreatedBy)
  me.dtCreated.setnow()
  me.sCreatedBy = sCreatedBy
end function me

function tCustInfo.copy(tCustInfo me)
  tCustInfo copy

  copy =@ tCustInfo.new(me.sCreatedBy)
  copy.sCustID = me.sCustID
  copy.sFirstname = me.sFirstname
  copy.sLastname = me.sLastname
  copy.dtCreated = me.dtCreated
end function copy

This example shows a user-defined type which implements a new and a copy method. The copy() method is implemented so that it produces an exact copy rather than a copy with a potentially new creator ID and new creation datetime.

Export and Readonly

You may have noticed a keyword I haven’t mentioned yet: export this keyword ensures that the type is visible outside the module, this is useful as the file can then be added to a project as a pre-compiled module (Library) that can be added to a project either at compilation or at runtime.

Note: Functions do not require the export keyword as they are made available within the type itself.

On the subject of keywords, there is one more I haven’t mentioned yet: readonly this is because it doesn’t occur in the normal use of SIMPOL and is used mostly in the advanced stages of programming while compiling large custom types similar to SIMPOL’s included libraries


Examples

The following are examples from other tutorials of varying difficulty to help you get to further familiarise yourself with custom types in SIMPOL

Example 1

This is an example required for our drilldown library guide

Before we can get into the meat of this tutorial we must first create a custom type that will hold a reference to our table.

type drillapp (application)
reference
application __app resolve
type(db1table) customer
end type

This type only uses the reference and resolve keywords. Reference is the default for any new variable and simply means our custom type will now refer to a value. Resolve is a more specialised use case, it allows us to call methods from other types in our own. In this case, we can now call methods from the application type by referencing drillapp

Initialising drillapp

Because we are resolving a variable we should declare a new() method:

function drillapp.new(drillapp me)

As this a more advanced tutorial I will leave you to declare all the relevant variables as they come up (or just look at the full source code at the bottom of the page)

me.__app =@ application.new(appiconfile="", iconimagetype="", inifilename="", apptitle =sAPPTITLE)
me.__app.__ =@ me

This code declares a new application and refers it to our __app variable, it then assigns drillapp to be a wrapper object for this application by referencing it to the __ variable. This is done so we can reach the container object from the contained object, as well as giving our new type the type tag of (application) so it can be passed to functions expecting such a type

The next thing we will be doing is declaring our onexitrequest function for the application

me.onexitrequest.function =@ exit

The first thing to note is that our drillapp has no onexitrequest declared, this is only possible since we have now declared and resolved our __app variable. The second thing to note is that exit is not a standard function so we will need to declare it (this will not be done here)

Appwindow

After we have declared our application we should create a window for it to stay in, an appwindow to be precise. In this example, this will not be very difficult as we are not implementing a menu, toolbar, or status bar so nothing fancy will need to be done

appw =@ appwindow.new(me,visible=.false,mb=.nul,tb=.nul,sb=.nul,border="simple",maxbutton=.false)

We need to start the window as invisible as we draw the form on before making it visible, we will also be using a simple border instead of the standard sizeable one as we don’t want the user to adjust the size, to accompany this we have also disabled the maximise button

Getting the database

This is the most basic way of getting a database file since it is only one database, and one table opened using SBME1. Nevertheless, it is important to know how to open it properly

The first thing we need to do is get the location of our CUSTOMER database and then open that database

 file = getpublicdatadir() + "\SIMPOL Business\customer.sbm"
src =@ me.opendatasource("sbme1", file, appw, error=e)

The database we will be using can be found in your public documents directory, we will then open this file

Opening the table

The next thing we need to do is open the table itself, and reference it back to our type

t =@ appw.opendatatable(src,"CUSTOMER",error=e)
me.customer =@ t
ok = .true

There is a mechanism in place at the very end of this method that will set the drillapp to .nul if something has gone wrong in the initialisation. This is done by declaring a boolean as .false at the top and only changing this boolean to .true after the table has been opened. After exiting all the error checking if statements the following code will check if everything happened as expected

if not ok
me =@ .nul
end if

Further notes

This type has no additional functions and is very basic, in more advanced cases you would add additional methods the example below is an example of a more complicated custom type

Example 2

 


Source Code

Example 1 Source Code

type drillapp (application)
  reference 
  application __app resolve
  type(db1table) customer
end type

function drillapp.new(drillapp me)
  appwindow appw
  datasourceinfo src
  type(db1table) t
  integer e
  boolean ok
  string file
  
  ok = .false
  e = 0
  
  me.__app =@ application.new(appiconfile="", iconimagetype="", inifilename="", \ 
apptitle =sAPPTITLE) me.__app.__ =@ me me.onexitrequest.function =@ exit me.running = .true appw =@ appwindow.new(me,visible=.false,mb=.nul,tb=.nul,sb=.nul,border="simple", \
maxbutton=.false) if appw =@= .nul wxmessagedialog(appw.w, "Error creating window", sAPPMSGTITLE,icon="error") else file = getpublicdatadir() + "\SIMPOL Business\customer.sbm" src =@ me.opendatasource("sbme1", file, appw, error=e) if src =@= .nul wxmessagedialog(appw.w,"Error opening customer.sbm file",sAPPMSGTITLE,icon="error") else t =@ appw.opendatatable(src,"CUSTOMER",error=e) if t =@= .nul wxmessagedialog(appw.w,"Error opening the 'Customer' table", \
sAPPMSGTITLE,icon="error") else me.customer =@ t ok =.true end if end if end if if not ok me =@ .nul end if end function me