[Note: Second in a series of articles on Microsoft C-Omega.
Earlier article: Introducing C-Omega]
C-Omega has some very special in-built types that build on the concept of streams. When used in conjunction with streams, these can give developers very powerful access to complex type definitions.
For example, C-Omega supports defining types as content classes. This is best illustrated using one of the sample programs that come as part of the C-Omega installation package.
Consider the following class that defines an Email content class and some associated methods:
public class Email {
// content of Email message
struct{
struct{
string From;
string To;
string? Subject;
} Header;
struct{
string P;
}* Body;
}
// methods of Email message
public static Email Vacation(DateTime d, TimeSpan s, string to){
return <Email>
<Header>
<From>John Doe</From>
<To>{to}</To>
<Subject>OFF</Subject>
</Header>
<Body><P>I am OFF from {d} until {d+s}.</P></Body>
</Email>;
}
}
Notice how an unlabelled structure is placed in the class Email. The Email type in C-Omega terms is known as a content class. It is similar to defining a XSD in XML. The Email type is now a complex type that holds a complex content definition.
The structure is made up of two sub-structures: Header and Body.
struct{
struct{
string From;
string To;
string? Subject;
} Header;
struct{
string P;
}* Body;
}
The string? type is a stream type which means that the Subject variable is an optional string. So if the value was null, it would assume that the field does not exist and if the value had something it would assume that as a string type.
The Body structure is of stream type struct* indicating that it is a stream similar to the one we saw in our earlier example. This allows us to iterate lazily through the body for multiple instances of string P.
There are other modifiers like these in C-Omega and they are termed as the Generalized Access Modifiers. The other modifiers available are:
! – Used for Possibly Null modifications
+ – Used for May Have More Than One value modifications
We will take a more detailed look at this in future articles.
Coming back to our Email class, the next definition is of a static method called Vacation that returns a value of the type Email. Remember that Email is a content class and so the type Email refers to a value as defined by the content definition. Note the convenient way in which the XML content can be directly used as a value and the way the other types can be substituted in the value by enclosing their variable names within { and }.
The above Email class translates in XSD as:
<element name="Email">
<complexType>
<sequence>
<element name="Header">
<complexType>
<sequence>
<element name="From" type="string"/>
<element name="To" type="string"/>
<element name="Subject" type="string" minOccurs="0" maxOccurs="1"/>
</sequence>
</complexType>
</element>
<element name="Body">
<complexType>
<sequence minOccurs="1">
<element name="P" type="string"/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
For a DTD version of the class, it can be translated as:
<!ELEMENT Email (Header, Body)>
<!ELEMENT Header (From, To, Subject?)>
<!ELEMENT Body (P+)>
<!ELEMENT From (#PCDATA)>
<!ELEMENT To (#PCDATA)>
<!ELEMENT Subject (#PCDATA)>
<!ELEMENT P (#PCDATA)>
That is where the real power of Content Classes and the special data types supported by C-Omega come into play. There are more types like these that we will explore in the future.
The concept also uses something called Anonymous Structs (Tuple Types) that we will discuss in future articles.
Let us now look at a consumer for the above class:
public class Demo {
public static void Main() {
Email hello =
<Email>
<Header>
<From>Bill</From>
<To>Steve</To>
</Header>
<Body>
<P>Hi Steve,</P>
<P>It is time for Coffee.</P>
</Body>
</Email>;
// print all members of header of the hello message
foreach(it in hello.Header.*){ Console.WriteLine(it); };
// print all paragraphs in the body of the hello message
foreach(it in hello.Body.P){ Console.WriteLine(it); };
// print the body of a vacation message using transitive query
vacation = Email.Vacation(DateTime.Now, new TimeSpan(5,0,0),"Wolfram");
foreach(it in vacation...P){ Console.WriteLine(it); };
Console.ReadLine();
}
}
The above consumer consumes Email in two ways. The first time a static instance of the Email class called hello is set to an Email value. The value is as defined by the content definition.
The hello.Header.* represents a stream pointing to the struct Header. The it keyword is used for iterating lazily through the stream.
The second consumption of the Email class makes a call to the static method Vacation and passes the arguments d, s and to. These arguments get substituted and an Email type with the substituted values is returned.
Notice that the variable vacation in the consumer class did not have to be defined as type Email because C-Omega can discover the types of variables dynamically.
The output for the above program as expected:
Bill
Steve
Hi Steve,
It is time for Coffee.
I am OFF from 08/19/2004 00:34:07 until 08/19/2004 05:34:07.
Support for XPath and XQuery
C-Omega supports XPath and XQuery-like expressions natively. An example piece of code is shown below.
A XQuery such as:
for $b in $bs/book
return
<result>
{$b/title} {$b/author} </result>
can be written in C# as:
foreach (b in bs.book)
{
yield return <result>
{b.title}
{b.author}
</result>;
}
Cool, eh?
It also has some support for filters, descendant member access and queries by type. To demonstrate these features, we go back to our Email example.
If you added one more method to the Email class like this:
class Email
{
.
.
.
public virtual bool MustRead(){
bool IsInteresting(string s){
return
( s.IndexOf("URGENT") >= 0
|| s.IndexOf("YOUR ASSISTANCE") >= 0
|| s.IndexOf("MILLION") >= 0
|| s.IndexOf("100% RISK FREE") >= 0
);
};
return this...string::*[IsInteresting(it)] == null;
}
}
And then another class called Spam like this:
public class Spam {
public static Email Sample =
<Email>
<Header>
<From>Koffi@hotmail.com</From>
<To>undisclosed recipients</To>
<Subject>URGENT ASSISTANCE NEEDED</Subject>
</Header>
<Body>
<P>
WITH OUR POSITIONS, WE HAVE SUCCESSFULLY SECURED
FOR OURSELVES THE SUM OF THIRTHY ONE MILLION,
FIVE HUNDRED THOUSAND UNITED STATES DOLLARS (US$31.5M).
THIS AMOUNT WAS CAREFULLY MANIPULATED
BY OVER-INVOICING OF AN OLD CONTRACT.
</P>
<P>
IT HAS BEEN AGREED THAT THE OWNER OF THE ACCOUNT
WILL BE COMPENSATED WITH 30% OF THE REMITTED FUNDS,
WHILE WE KEEP 60% AS THE INITIATORS AND 10% WILL BE
SET ASIDE TO OFFSET EXPENSES AND PAY THE NECESSARY TAXES.
</P>
<P>THIS TRANSACTION IS 100% RISK FREE.</P>
</Body>
</Email>;
}
Spam is a simple class that returns a value of the type Email. The return value is a typical spam email that we want to filter.
The MustRead method allows us to scan through an Email message. How this works is by applying a filter and a query by type in conjunction to the message.
The line
return this...string::*[IsInteresting(it)] == null;
has these two operations.
The this...string essentially means all members in the existing class of type string irrespective of the member name. That is what the triple dot operator denotes. This is further returned as a stream by the * operator and is then applied a filter IsInteresting(it) which essentially iterates through the stream and returns false if any of the conditions in the IsInteresting definition are met.
If no result was obtained, then the end value is null and hence true is returned.
Finally, we add one more line of code to our Demo class:
// check if the spam message must be read
Console.WriteLine("Must read = {0}", Spam.Sample.MustRead());
This gives us the added output:
Must read = false
Been a real long write-up. If you have reached this far, you should look forward to more. We’ve begun to scratch the surface.