XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Introducing Comega

January 12, 2005

Introduction

One of the main reasons for XML's rise to prominence as the lingua franca for information interchange is that, unlike prior data interchange formats, XML can easily represent both rigidly structured tabular data (e.g., relational data or serialized objects) and semi-structured data (e.g., office documents). The former tends to be strongly typed and is typically processed using object⇔XML mapping technologies, while the latter tends to be untyped and is usually processed using XML technologies like DOM, SAX, and XSLT. However in both cases, there is a disconnect for developers processing XML using traditional object-oriented programming languages.

In the case of processing strongly typed XML using object⇔XML mapping technologies, there is the impedance mismatch between programming language objects and XML schema languages like DTDs or W3C XML Schema. Notions such as the distinction between elements and attributes, document order, and content models that specify a choice of elements are all intrinsic to XML schema languages but have no counterpart in standard object-oriented programming. These mismatches tend to lead to some contortions and lossiness when mapping XML to objects. Processing untyped XML documents using technologies such as XSLT or the DOM has a different set of issues. In the case of XSLT, or other XML-specific languages like XQuery, the developer has to learn a whole other language to effectively process XML as well as their programming language of choice. Often, all the benefits of the IDE of the host language--for example, compile time checking and tooltips--cannot be utilized when processing XML. In the case of processing XML with DOM or SAX, developers often complain that the code they have to write tends to become unwieldy and cumbersome.

As design patterns and APIs for performing particular tasks become widely used, they sometimes become incorporated into programming languages. Programming languages like Java and C# have promoted concepts which exist as design patterns and API calls in other languages, such as native string types, memory management via garbage collection, and event handling to core constructs within the language. This evolutionary process has now begun to involve XML. As XML has grown in popularity, certain parties have begun to integrate constructs for creating and manipulating XML into mainstream programming languages. The expectation is that making XML processing a native part of these programming languages will ease some of the problems facing developers that use traditional XML processing techniques.

Two of the most notable, recent examples of XML being integrated into programming languages are (C-Omega)--an extension of C# produced by Microsoft Research--and ECMAScript for XML (E4X)--an ECMAScript extension produced by ECMA International. This article provides an overview of the XML features of Cω; my next article will explore the E4X language. This article begins with an introduction to the changes to the C# type system made in Cω, followed by a look at the operators added to the C# language to enable easier processing of relational and XML data.

The Cω Type System

The goal of the Cω type system is to bridge the gap between relational, object, and XML data access by creating a type system that is a combination of all three data models. Instead of adding built-in XML or relational types to the C# language, the approach displayed by the Cω type system has been to add certain general changes to the C# type system that make it more conducive for programming against both structured relational data and semi-structured XML data.

A number of the changes to C# made in Cω make it more conducive for programming against strongly typed XML, specifically XML constrained by W3C XML Schema. Several concepts from XML and XML Schema have analogous features in Cω: document order, the distinction between elements and attributes, having multiple fields with the same name but different values, and content models that specify a choice of types for a given field. A number of these concepts are handled in traditional object⇔XML mapping technologies but it is often with awkwardness. Cω makes programming against strongly typed XML as natural as programming against arrays or strings in traditional programming languages.

Streams

Streams in Cω are analogous to sequences in XQuery and XPath 2.0 and .NET 2.0's System.Collections.Generic.IEnumerable<T> type. With the existence of streams, Cω promotes the concept of an ordered, homogenous collection of zero or more items into a programming language construct. Streams are a fundamental aspect of the Cω type system upon which a number of the other extensions to C# rest.

A stream is declared by appending the operator * to the type name in the declaration of the variable. Typically, streams are generated using iterator functions. An iterator function is a function that returns an ordered sequence of values by using a yield statement to return each value in turn. When a value is yielded, the state of the iterator function is preserved and the caller is allowed to execute. The next time the iterator is invoked, it continues from the previous state and yields the next value. Iterators functions in Cω work like the iterators functions planned for C# 2.0 The most obvious difference between iterator functions in Cω and iterator functions in C# is that the former return a stream (T*) while C# 2.0 iterators return an enumerator (IEnumerator<T>). However, there is little difference in behavior when interacting with a stream or an enumerator. The more significant difference is that, just like sequences in XQuery, streams in Cω cannot contain other streams. When multiple streams are combined, the results are flattened into a single stream. For example, appending the stream (4,5) to the stream (1,2,3) results in a stream containing five items (1,2,3,4,5), not a stream with four items (1, 2, 3, (4, 5)). In C# 2.0, it isn't possible to combine multiple enumerators in such a manner, although it is possible to create an enumerator of enumerators.

The following iterator function returns a stream containing the three books in the Lord of the Rings Trilogy.


 public string* LoTR(){

    yield return "The Fellowship of the Ring"; 
    yield return "The Two Towers";
    yield return "The Return of the King";

  }

The results can be processed using a traditional C# foreach loop:


  public void PrintTrilogyTitles(){

    foreach(string title in LoTR()) 
      Console.WriteLine(title);
  }

A powerful feature of Cω streams is that one can invoke methods on a stream which are then translated to subsequent method calls on each item in the stream. The following method shows an example of this feature in action:


  public void PrintTrilogyTitleLengths(){

    foreach(int size in LoTR().Length) 
      Console.WriteLine(size);
  }

This method call results in the value of the Length property being invoked on each string returned by the PrintTrilogyTitles() method. The ability to access the properties of the contents of a stream allows XPath-style queries over object graphs.

There is also the concept of an apply-to-all-expressions construct which allows one to apply an anonymous method directly to each member of a stream. These anonymous methods may contain the special variable it which is bound to each successive element of the iterated stream. Below is an alternate implementation of the PrintTrilogyTitles() method which uses this construct.


  public void PrintTrilogyTitles(){
  
    LoTR().{Console.WriteLine(it)};
  }

Choice Types and Nullable Types

Cω's choice types are very similar to union types in programming languages like C and C++, the | operator in DTDs, and the xs:choice element in W3C XML Schema. The following is an example of a class that uses a choice type


public class NewsItem{

    string title;
    string author;
    choice {string pubdate; DateTime date;};
    string body;    
}

In this example, an instance of the NewsItem class can either have a pubdate field of type System.String or a date field of type System.DateTime but not both. It should be noted that the Cω compiler enforces the constraint that each field in the choice should have a different name, otherwise there would be ambiguity as to what type was intended when the field is accessed. The way fields in a choice type are accessed in Cω differs from union types in C and C++. In C and C++, the programmer has to keep track of what type the value in a particular union type represents, since no static type checking is done by the compiler. Cω union types are statically checked by the compiler which allows one the declare them as such:


  choice{string;DateTime;} x =  DateTime.Now;
  choice{string;DateTime;} y =  "12/12/2004";

However, there is still the problem of how to declare a type statically which may depend on a field in a choice type that does not exist. For example, in the above sample, x.Length is not a valid property access if the variable is initialized with an instance of System.DateTime but returns 10 when initialized with the string "12/12/2004". This is where nullable types come into play.

In both the worlds of W3C XML Schema and relational databases, it is possible for all types to have instances whose value is null. Java and existing versions of C# do not allow one to assign null to an integer or floating point value type. However, when working with XML or relational data, it is valuable to be able to state that null is a valid value for a type. In such cases, you wouldn't want property accesses on the value to result in NullReferenceExceptions being thrown. Nullable types make this possible by mapping all values returned by a property access to the value null. Below are some examples of using nullable types:


   string? s = null;
   int? size = s.Length;  // returns null instead of throwing NullReferenceException
  
   if(size == null)
     Console.WriteLine("The value of size is NULL");

   choice{string;DateTime;} pubdate =  DateTime.Now;
   int? dateLen  = pubdate.Length; //works since it returns null because 
        Length is a property of System.String
   int dateLen2  = (int) pubdate.Length; //throws NullReferenceException 

This feature is similar to but not the same as nullable types in C# 2.0. A nullable type in C# 2.0 is an instance of Nullable<T> which contains a value and an indication whether the value is null or not. This is basically a wrapper for value types such as ints and floats that can't be null. Cω takes this one step further with the behavior of returning null instead of throwing a NullReferenceException on accessing a field or property of a nullable type whose value is null.

Anonymous Structs

Anonymous structs are analogous to the xs:sequence element in W3C XML Schema. Anonymous structs enable one to model certain XML-centric notions in Cω, such as document order and the fact that an element may have multiple child elements with the same name that have different values. An anonymous struct is similar to a regular struct in C# with a few key distinctions:

  1. There is no explicit type name for an anonymous struct.
  2. Fields within an anonymous struct are ordered, which allows them to be accessed via the array index operator.
  3. The fields within an anonymous struct do not have to have a name; they can just have a type.
  4. An anonymous struct can have multiple fields with the same name. In this case, accessing these fields by name results in a stream being returned.
  5. Anonymous structs with similar structure (i.e. same member types in the same order) are compatible, and variables of such structs can be assigned back & forth.

The following examples highlight the various characteristics of anonymous structs in Cω:


  struct{ int; string; 
          string; DateTime date; 
	  string;} x =          new {47, "Hello World", 
				     "Dare Obasanjo", date=DateTime.Now, 
				     "This is my first story"};
    Console.WriteLine(x[1]); 
    DateTime pubDate = x.date; 

    struct{ long; string; string; 
            DateTime date; string;} newsItem = x; 
    Console.WriteLine(newsItem[1] + " by " + newsItem[2] + " on " + newsItem.date);
    
    struct {string field; 
            string field; 
	    string field;} field3 = new {field="one",
					 field="two", 
					 field="three"};

    string* strField = field3.field; 
    //string strField = field3.field doesn't work since field3.field returns a stream 

    struct {int value; string value;} tricky = new {value=10, value="ten"};
    choice {int; string;}* values = tricky.value;     

Content Classes

A content class is a class that has its members grouped into distinct units using the struct keyword. To some degree, content classes are analogous to DTDs in XML. The following content class for a Books object


public class Books{
  
  struct{
    string title;
    string author;
    string publisher;
    string? onloan;
  }* Book;      

} 

is analogous to the following DTD


<!ELEMENT Books (Book*)>
<!ELEMENT Book  (title, author,publisher, onloan?)>
<!ELEMENT title  (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT onloan (#PCDATA)>

or the following W3C XML Schema


<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="Books">
    <xs:complexType>
      <xs:sequence minOccurs="0" maxOccurs="0">
	<xs:element name="Book">
	  <xs:complexType>
	    <xs:sequence>
	      <xs:element name="title" type="xs:string" />
	      <xs:element name="author" type="xs:string" />
	      <xs:element name="publisher" type="xs:string" />
	      <xs:element name="onloan" type="xs:string" minOccurs="0"/>
	    </xs:sequence>
	  </xs:complexType>
	</xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>

The following code sample shows how the Books object would be used


using Microsoft.Comega;
using System;

public class Books{
  
  struct{
    string title;
    string author;
    string publisher;
    string? onloan;
  }* Book;      


  public static void Main(){

    Books books = new Books();

    books.Book = new struct {title="Essential.NET", author="Don Box", 
	     publisher="Addison-Wesley", onloan =  (string?) null};

    Console.WriteLine((string) books.Book.author + " is the author of " + 
	     (string) books.Book.title);
  }
}

Pages: 1, 2

Next Pagearrow







close