Opaque data types

OmniMark permits the creation of new types to provide external functions with the data that they require. These new types are called opaque types, because the data in an opaque type is not visible to any command in the OmniMark programming language. OmniMark programs can only modify the data in an opaque item or shelf by using set to copy from another item or shelf of the same type, or by calling an external function to do the modification.

You can define as many different opaque data types as you need, and you can use them anywhere that you would use the OmniMark types (integer, stream and switch). Opaque data shelves and items are managed by the OmniMark shelf management facility. OmniMark will prevent a set command from assigning a variable of one opaque type to a value of another, just as the assignment of an integer variable to a switch value is prevented. At the end of a do ... done block of code, a local opaque shelf will go out of scope, releasing resources, just like a local stream shelf.

In OmniMark code, opaque datatypes are referenced just like conventional variables. The name of the opaque becomes the data type for the variable. Anywhere you can declare an OmniMark variable type, you can declare an opaque type. The following code declares two opaque variables, one for a tcp.service and one for a tcp.connection:

  local tcp.service my-service
  local tcp.connection my-connection

In general, you can use an opaque datatype just as you would any OmniMark variable. There is one difference, however. All variables are handles to objects of greater or lesser complexity. When you perform an action on a variable, the corresponding object is manipulated in the appropriate way. Usually you can ignore this subtlety (the general point of a high-level programming language is to avoid your having to worry about things like this). Some opaque types, however, serve as proxies for real objects in the external world, and there are limits on what OmniMark or an opaque type can do to those objects.

Consider an ordinary OmniMark variable of type integer. I can make a copy of that integer and have two separate integers:

  process
     local integer a
     local integer b
  
     set a to 12
     set b to a

Here I have two separate integers, a and b, each containing the number 12. If I subsequently set b to 6, a will still be 12. But if I have an FTPConnection opaque type and I make a copy of it, I obviously don't have two FTP servers. Perhaps less obviously, I also don't have two connections to the same FTP server:

  process
     local FTPConnection c
     local FTPConnection d
     set c to FTPConnectionOpen server "ftp.omnimark.com"
                                  user "anonymous"
                              password "[email protected]"
     set d to c

In this code, setting c to an open connection involved actually contacting the server and actually logging in. You can't create a new connection without communicating with the server with specific information. Setting d to c, therefore, does not give you two separate connections, d and c. What does it give you? Simply two handles to the same connection—two opaque type variables referring to the same object. If I close d as follows:

  FTPConnectionClose d
then the connection represented by the variable c will also be closed because it is the same connection.

If, on the other hand, I assign a new connection to d, using

  set d to FTPConnectionOpen server   "ftp.stilo.com"
                             user     "anonymous"
                             password "[email protected]"

the connection referenced by c will not be modified. In general, set action never modifies an existing opaque value.

This can have an important impact on the operation of the OmniMark save command. save creates a temporary variable with the same name and value as a global variable. It has the lifespan of a local variable, but the visibility of a global variable. When the temporary variable goes out of scope, the global becomes visible again. Normally, the temporary copy created by a save is just that: a copy. You can modify it without worrying about changing the value of the underlying global. But with a modifiable opaque, doing a save just creates a temporary opaque variable that is a handle to the same opaque as the original. Since it has the same name as the global and references the same thing, the effect of using save on a modifiable opaque type is, effectively, nothing. The exact same name refers to the exact same object before, during, and after the save.

This is not to say that save is useless on all opaque datatypes. It depends on the kind of object the component represents and how the component is implemented. For instance, the float datatype, which is used to represent floating point numbers, is not modifiable. So the following code would work just as in the case of an integer:

  process
     local float a
     local float b
  
     set a to 12.3
     set b to a

Consult the documentation for the opaque type you are using to determine whether it is copyable or not.