Sequence Types

Sequences are special types and can be thought of as collections of items, similar to arrays.

In contrast to arrays, sequences do not imply a specific form of data storage, but can represent any collection of elements that is accessible in a specific order. This could be an array (and as a matter of fact, all arrays can be treated as a sequence) or a different data store, such as a linked list, a binary tree or a custom collection implementation.

Sequences can also represent non-static data that is retrieved or generated on the fly, as the sequence is enumerated – for example, you could implement a sequence that calculates all digits of Pi, or retrieves RSS headlines downloaded from a server.

In Oxygene, sequences are represented by the sequence of keyword combination (analogous to the array of syntax), so a variable holding a sequence of strings would be defined as follows:

var Names: sequence of String;

In all four languages, sequences are also available via the ISequence<T> interface type.

var Names: ISequence<String>;
ISequence<String> Names;
var Names: ISequence<String>?
ISequence<String> Names;

A variable defined like this would then be capable of holding any type of sequence – be it an array, a standard generic List<String> collection class or a custom sequence implementation.

Because sequences are an abstract type concept that represents the concept of how data is accessed, but not the way the data is stored (unlike, for example, arrays), you cannot directly instantiate a "sequence" in the way that you would instantiate an array. Instead, you will usually instantiate a specific type that implements sequence behavior. For example, the following assignments would be valid:

Names := ['Peter', 'Paul'];      // arrays are sequences, too
Names := new List<String>();    // the List type is a sequence
Names := GatherData();         // call a function that returns a sequence
Names = ['Peter', 'Paul'];       // arrays are sequences, too
Names = new List<String>();     // the List type is a sequence
Names = GatherData();          // call a function that returns a sequence
Names = ['Peter', 'Paul']        // arrays are sequences, too
Names = List<String>()          // the List type is a sequence
Names = GatherData()           // call a function that returns a sequence
Names = ['Peter', 'Paul'];       // arrays are sequences, too
Names = new List<String>();     // the List type is a sequence
Names = GatherData();          // call a function that returns a sequence

Once an instance of a sequence is obtained, it can be used in a variety of ways, regardless of the way the actual data is stored or generated. Most importantly, it can be looped over using the for each constructs, or accessed using the query operations and LINQ Standard Query Operators.

Once again, it is important to realize that the sequence merely defines a front-end for accessing data, but is entirely decoupled from the way this data exists. If a sequence references an array or List object, it will of course directly access the in-memory data contained in this array or list. However, if the sequence references a custom implementation or an iterator, data might be fetched from external sources or generated on-the-fly, as needed.

In this second case, individual elements of the sequence will not be obtained until code is used to access and enumerate the sequence, meaning that little or no cost will be involved in generating the initial sequence reference. For example, a sequence backed by a database (such as the DA LINQ tables in Data Abstract) might contain hundreds or even thousands of records – but these records will not be transferred from the database server into local memory until the individual rows are accessed.

Type Mapping

  • On .NET, sequences map to the IEnumerable<T> FCL interface.
  • On Cocoa, sequences map to the INSFastEnumeration<T> interface provided by Foundation.
  • On Java, sequences map to the IQueryable<T> JDK interface.
  • On Island, sequences map to the IEnumerable<T> in Island RTL.

Queryable Sequences

Queryable sequences are special kinds of sequences currently available on .NET only, where the LINQ lambda expressions are turned into an expression tree instead of code, so it can be transported over a network. This allows access to features such as LinqToSQL for accessing Microsoft SQLServer, and Data Abstract comes with a LINQ provider that can use this feature.

In line with Oxygene's language-level support for sequences, the queryable sequence keyword combination can be used to refer to these:

var
  r: queryable sequence of Customer := from x in Tables.Customers
                                         where x.Name = 'ALKI' select x;

A queryable sequence is an alias to System.Linq.IQueryable<T>, and in C# and Swift (and Oxygene as well), you can use the IQueryable<T> name to refer to this type.

Parallel Sequences

Also exclusive to .NET at this time, parallel sequences are sequences that leverage the Microsoft Parallel Framework or .NET 4.0 and later for multi-threaded access. Parallel sequences are executed over multiple threads when they're called. They are aliases to the System.Linq.IParallelEumerable<T> type.

Once again, Oxygene has a special syntax to express these sequence types:

var
  r: parallel sequence of string := Enumerable.Range(0, 1000).AsParallel();

while C# and Swift (and Oxygene), can use the IParallelEumerable<T> interface.

Sequence Operators

Sequences support the + operator when defined as a sequence of T in Oxygene or as ISequence<T> in all four languages. The + operator will call the Concat() method to create a new enumerable that returns two lists sequentially. This operator works on all sequence types.