Sequences
9 min read
7 min read
10 min read
2 min read
2 min read
2 min read
7 min read
3 min read
2 min read
Sequence Syntax
Sequences are variable-length collections of elements:
module M{ sequence<Fruit> FruitPlatter;}A sequence can be empty — that is, it can contain no elements, or it can hold any number of elements up to the memory limits of your platform.
Sequences can contain elements that are themselves sequences. This arrangement allows you to create lists of lists:
module M{ sequence<FruitPlatter> FruitBanquet;}Sequences are used to model a variety of collections, such as vectors, lists, queues, sets, bags, or trees. (It is up to the application to decide whether or not order is important; by discarding order, a sequence serves as a set or bag.)
Language Mapping
Default Mapping
Here is the definition of our FruitPlatter sequence once more:
sequence<Fruit> FruitPlatter;The Slice compiler generates the following definition for the FruitPlatter sequence:
using FruitPlatter = std::vector<Fruit>;As you can see, the sequence simply maps to a standard std::vector, so you can use the sequence like any other vector. For example:
// Make a small platter with one Apple and one OrangeFruitPlatter p;p.push_back(Fruit::Apple);p.push_back(Fruit::Orange);Customizing the Sequence Mapping with cpp:type
cpp:typeThe cpp:type:c++-type metadata directive allows you to map a given Slice type, field or parameter to the C++ type of your choice.
For example, you can override the default mapping of a Slice sequence type:
[["cpp:include:list"]]
module Food{ enum Fruit { Apple, Pear, Orange };
["cpp:type:std::list<Food::Fruit>"] sequence<Fruit> FruitPlatter;}With this metadata directive, the Slice sequence now maps to a C++ std::list instead of the default std::vector:
#include <list>
namespace Food{ using FruitPlatter = std::list<Food::Fruit>;
// ...}The Slice to C++ compiler takes the string following the cpp:type: prefix as the name of the mapped C++ type. For example, we could use ["cpp:type:::std::list<::Food::Fruit>"]. In that case, the compiler would use a fully-qualified name to define the type:
using FruitPlatter = ::std::list<::Food::Fruit>;Note that the code generator inserts whatever string you specify following the cpp:type: prefix literally into the generated code. We recommend you use fully qualified names to avoid C++ compilation failures due to unknown symbols.
Also note that, to avoid compilation errors in the generated code, you must instruct the compiler to generate an appropriate include directive with the cpp:include file metadata directive. This causes the compiler to add the line
#include <list>to the generated header file.
In addition to modifying the type of a sequence itself, you can also modify the mapping for particular return values or parameters. For example:
[["cpp:include:list"]][["cpp:include:deque"]]
module Food{ enum Fruit { Apple, Pear, Orange }
sequence<Fruit> FruitPlatter;
interface Market { ["cpp:type:std::list<::Food::Fruit>"] FruitPlatter barter(["cpp:type:std::deque<::Food::Fruit>"] FruitPlatter offer); }}With this definition, the default mapping of FruitPlatter to a C++ vector still applies but the return value of barter is mapped as a list, and the offer parameter is mapped as a deque.
Instead of std::list or std::deque, you can specify a type of your own as the sequence type, for example:
[["cpp:include:FruitBowl.h"]]
module Food{ enum Fruit { Apple, Pear, Orange }
["cpp:type:FruitBowl"] sequence<Fruit> FruitPlatter;}With these metadata directives, the compiler will use a C++ type FruitBowl as the sequence type, and add an include directive for the header file FruitBowl.h to the generated code.
The class or template class you provide must meet the following requirements:
- The class must have a default constructor.
- The class must have a copy constructor.
If you use a class that also meets the following requirements
- The class has a single-argument constructor that takes the size of the sequence as an argument of unsigned integral type.
- The class has a member function
sizethat returns the number of elements in the sequence as an unsigned integral type. - The class provides a member function
swapthat swaps the contents of the sequence with another sequence of the same type. - The class defines
iteratorandconst_iteratortypes and providesbeginandendmember functions with the usual semantics; its iterators are comparable for equality and inequality.
then you do not need to provide code to marshal and unmarshal your custom sequence – Ice will do it automatically.
Less formally, this means that if the provided class looks like a vector, list, or deque with respect to these points, you can use it as a custom sequence implementation without any additional coding.
Span Mapping for Sequence Parameters
When you give a sequence parameter to Ice for marshaling, this parameter is passed by const reference. Take for example:
sequence<int> IntSeq;
interface Collector{ void reportValues(IntSeq values);}With the default mapping, the proxy functions look like:
using IntSeq = std::vector<std::int32_t>;
class CollectorPrx : ...{public: void reportValues(const IntSeq& values, ...);};You can change this default mapping for “outgoing” parameters to a std::span with the metadata directive ["cpp:view-type:std::span<const T>"] (or ["cpp:view-type:std::span<T>"]) where T is the mapped element type.
With our example above:
void reportValues(["cpp:view-type:std::span<const std::int32_t>"] IntSeq values);changes the mapping to:
void reportValues(std::span<const std::int32_t> values, ...);This span mapping can help reduce copies in the caller.
Array Mapping for Sequence Parameters
In addition to the default and custom mappings of sequence types as a whole, you can use metadata ["cpp:array"] to map a single operation parameter of type sequence to a pair of pointers.
The array mapping for sequence parameters applies only to:
- In parameters, on the client-side and on the server-side
- Out and return parameters provided by the Ice runtime to AMI callbacks
- Out and return parameters provided to marshaled results or AMD callbacks
For example:
interface File{ void write(["cpp:array"] Ice::ByteSeq contents);}The cpp:array metadata directive instructs the compiler to map the contents parameter to a pair of pointers. With this directive, the write function on the proxy has the following signature:
void write( const std::pair<const std::byte*, const std::byte*>& contents, const Ice::Context& = Ice::noExplicitContext);To pass a byte sequence to the server, you pass a pair of pointers; the first pointer points at the beginning of the sequence, and the second pointer points one element past the end of the sequence.
Similarly, for the server side, the write method on the skeleton has the following signature:
virtual void write( std::pair<const std::byte*, const std::byte*> contents, const Ice::Current& current) = 0;The passed pointers denote the beginning and end of the sequence as a range [first, last) (that is, they use the usual semantics for iterators).
The array mapping is useful to achieve zero-copy passing of sequences. The pointers point directly into the server-side transport buffer when receiving a request; this allows the runtime to avoid creating a vector to pass to the operation implementation, thereby avoiding both allocating memory for the sequence and copying its contents into that memory.
Ice for C# supports several different mappings for sequences. By default, sequences are mapped to arrays. You can use metadata directives to map sequences to a number of alternative types:
System.Collections.Generic.ListSystem.Collections.Generic.LinkedListSystem.Collections.Generic.QueueSystem.Collections.Generic.Stack- User-defined custom types that derive from
System.Collections.Generic.IEnumerable<T>.
The different mappings allow you to map a sequence to a container type that provides the correct performance trade-off for your application.
Array Mapping for Sequences
By default, the Slice-to-C# compiler maps sequences to arrays. Interestingly, no code is generated in this case; you simply define an array of elements to model the Slice sequence. For example:
sequence<Fruit> FruitPlatter;Given this definition, to create a sequence containing an apple and an orange, you could write:
Fruit[] fp = { Fruit.Apple, Fruit.Orange };Or, alternatively:
Fruit[] fp = new Fruit[2];fp[0] = Fruit.Apple;fp[1] = Fruit.Orange;The array mapping for sequences is both simple and efficient, especially for sequences that do not need to provide insertion or deletion other than at the end of the sequence.
Mapping to Predefined Generic Containers
With metadata directives, you can change the default mapping for sequences to use generic containers provided by C#. For example:
["cs:generic:List"] sequence<string> StringSeq;["cs:generic:LinkedList"] sequence<Fruit> FruitSeq;["cs:generic:Queue"] sequence<int> IntQueue;["cs:generic:Stack"] sequence<double> DoubleStack;The "cs:generic:<type>" metadata directive causes the slice2cs compiler to the map the corresponding sequence to one of the containers in the System.Collections.Generic namespace. For example, the Queue sequence maps to System.Collections.Generic.Queue<int> due to its metadata directive.
The predefined containers allow you to select an appropriate space-performance trade-off, depending on how your application uses a sequence. In addition, if a sequence contains value types, such as int, the generic containers do not incur the cost of boxing and unboxing and so are quite efficient. (For example, System.Collections.Generic.List<int> performs within a few percentage points of an integer array for insertion and deletion at the end of the sequence, but has the advantage of providing a richer set of operations.)
Generic containers can be used for sequences of any element type except classes. For sequences of classes, only List is supported because it provides the functionality required for efficient unmarshaling. Metadata that specifies any other generic type is ignored with a warning:
class MyClass{ // ...}
["cs:generic:List"]sequence<MyClass> MyClassList; // OK
["cs:generic:LinkedList"]sequence<MyClass> MyClassLinkedList; // IgnoredIn this example, sequence type MyClassList maps to the generic container System.Collections.Generic.List<MyClass>, but sequence type MyClassLinkedList uses the default array mapping.
Mapping to Custom Types
If the array mapping and the predefined containers are unsuitable for your application (for example, because you may need a priority queue, which does not come with .NET), you can implement your own custom containers and direct slice2cs to map sequences to these custom containers. For example:
["cs:generic:MyTypes.PriorityQueue"] sequence<int> Queue;This metadata directive causes the Slice Queue sequence to be mapped to the type MyTypes.PriorityQueue. You must specify the fully-qualified name of your custom type following the cs:generic: prefix. This is because the generated code prepends a global:: qualifier to the type name you provide; for the preceding example, the generated code refers to your custom type as global::MyTypes.PriorityQueue<int>.
Your custom type can have whatever interface you deem appropriate, but it must meet the following requirements:
- The custom type must derive from
System.Collections.Generic.IEnumerable<T>. - The custom type must provide a readable
Countproperty that returns the number of elements in the collection. - The custom type must provide an
Addmethod that appends an element to the end of the collection. - If (and only if) the Slice sequence contains elements that are Slice classes, the custom type must provide an indexer that sets the value of an element at a specific index. (Indexes, as usual, start at zero.)
As an example, here is a minimal class (omitting implementation) that meets these criteria:
public class PriorityQueue<T> : IEnumerable<T>{ public IEnumerator<T> GetEnumerator();
public int Count { get; }
public void Add(T element);
public T this[int index] { get; set; } // Needed for class elements only.
// Other methods and data members here...}Multi-Dimensional Sequences
Slice permits you to define sequences of sequences, for example:
enum Fruit { Apple, Orange, Pear }["cs:generic:List"] sequence<Fruit> FruitPlatter;["cs:generic:LinkedList"] sequence<FruitPlatter> Cornucopia;If we use these definitions as shown, the type of FruitPlatter in the generated code is:
System.Collections.Generic.LinkedList<System.Collections.Generic.List<Fruit>>Here the outer sequence contains elements of type List<Fruit>, as you would expect.
Now let us modify the definition to change the mapping of FruitPlatter to an array:
enum Fruit { Apple, Orange, Pear }sequence<Fruit> FruitPlatter;["cs:generic:LinkedList"] sequence<FruitPlatter> Cornucopia;With this definition, the type of Cornucopia becomes:
System.Collections.Generic.LinkedList<Fruit[]>The generated code now no longer mentions the type FruitPlatter anywhere and deals with the outer sequence elements as an array of Fruit instead.
Default Mapping
A Slice sequence maps to a Java array. This means that the Slice-to-Java compiler does not generate a separate named type for a Slice sequence.
For example:
sequence<Fruit> FruitPlatter;This definition simply corresponds to the Java type Fruit[]. Naturally, because Slice sequences are mapped to Java arrays, you can take advantage of all the array functionality provided by Java, such as initialization, assignment, cloning, and the length member. For example:
Fruit[] platter = { Fruit.Apple, Fruit.Pear };assert(platter.length == 2);Customizing the Sequence Mapping with java:type
java:typeThe java:type:instance-type[:formal-type] metadata directive allows you to map a given Slice type, field or parameter to the Java type of your choice.
The formal type is optional; the compiler uses a default value if one is not defined. The instance type must satisfy an is-A relationship with the formal type: either the same class is specified for both types, or the instance type must be derived from the formal type.
The Slice-to-Java compiler generates code that uses the formal type for all occurrences of the modified Slice definition except when the generated code must instantiate the type, in which case the compiler uses the instance type instead. The compiler performs no validation on your custom types. Misspellings and other errors will not be apparent until you compile the generated code.
For example, you can override the default mapping of a Slice sequence type:
module Food{ enum Fruit { Apple, Pear, Orange };
["java:type:java.util.LinkedList<Fruit>"] sequence<Fruit> FruitPlatter;}With this metadata directive, the Slice sequence now maps to a Java LinkedList instead of the default array.
It is your responsibility to use a type parameter for the Java class (Fruit in the example above) that is the correct mapping for the sequence's element type.
The compiler requires the formal type to implement java.util.List<E>, where E is the Java mapping of the element type. If you do not specify a formal type, the compiler uses java.util.List<E> by default.
Note that extra care must be taken when defining custom types that contain nested generic types, such as a custom sequence whose element type is also a custom sequence. The Java compiler strictly enforces type safety, therefore any compatibility issues in the custom type metadata will be apparent when the generated code is compiled.
Using the java:type Metadata Directive
java:type Metadata DirectiveYou can define custom type metadata in a variety of situations. The simplest scenario is specifying the metadata at the point of definition:
["java:type:java.util.LinkedList<String>"]sequence<string> StringList;Defined in this manner, the Slice-to-Java compiler uses java.util.List<String> (the default formal type) for all occurrences of StringList, and java.util.LinkedList<String> when it needs to instantiate StringList.
You may also specify a custom type more selectively by defining metadata for a field, parameter or return value. For instance, the mapping for the original Slice definition might be sufficient in most situations, but a different mapping is more convenient in particular cases. The example below demonstrates how to override the sequence mapping for the field of a structure as well as for several operations:
sequence<string> StringSeq;
struct S{ ["java:type:java.util.LinkedList<String>"] StringSeq seq;}
interface I{ ["java:type:java.util.ArrayList<String>"] StringSeq modifiedReturnValue();
void modifiedInParam(["java:type:java.util.ArrayList<String>"] StringSeq seq);
void modifiedOutParam(out ["java:type:java.util.ArrayList<String>"] StringSeq seq);}As you might expect, modifying the mapping for an operation's parameters or return value may require the application to manually convert values from the original mapping to the modified mapping. For example, suppose we want to invoke the modifiedInParam operation. The signature of its proxy operation is shown below:
void modifiedInParam(java.util.List<String> seq)The metadata changes the mapping of the seq parameter to java.util.List, which is the default formal type. If a caller has a StringSeq value in the original mapping, it must convert the array as shown in the following example:
String[] seq = new String[2];seq[0] = "hi";seq[1] = "there";IPrx proxy = ...;proxy.modifiedInParam(java.util.Arrays.asList(seq));Although we specified the instance type java.util.ArrayList<String> for the parameter, we are still able to pass the result of asList because its return type (java.util.List<String>) is compatible with the parameter's formal type declared by the proxy method. In the case of an operation parameter, the instance type is only relevant to a servant implementation, which may need to make assumptions about the actual type of the parameter.
Buffer Types
You can annotate sequences of certain primitive types with the java:buffer metadata directive to change the mapping to use subclasses of java.nio.Buffer. This mapping provides several benefits:
- You can pass a buffer to a Slice API instead of creating and filling a temporary array
- If you need to pass a portion of an existing array, you can wrap it with a buffer and avoid an extra copy
- Receiving buffers during a Slice operation also avoids copying by directly referencing the data in Ice's unmarshaling buffer
The following table lists each supported Slice primitive type with its corresponding mapped class:
| Primitive | Mapping |
|---|---|
byte | java.nio.ByteBuffer |
short | java.nio.ShortBuffer |
int | java.nio.IntBuffer |
long | java.nio.LongBuffer |
float | java.nio.FloatBuffer |
double | java.nio.DoubleBuffer |
The java:buffer directive can be applied to the initial definition of a sequence, in which case the mapping uses the buffer type for all occurrences of that sequence type:
["java:buffer"] sequence<int> Values;
struct Observation{ int x; int y; Values measurements;}We can construct an Observation as follows:
Observation obs = new Observation();obs.x = 5;obs.y = 9;obs.measurements = java.nio.IntBuffer.allocate(10);for(int i = 0; i < obs.measurements.capacity(); ++i){ obs.measurements.put(i, ...);}The java:buffer directive can also be applied in more limited situations to override a sequence's normal mapping:
sequence<byte> ByteSeq; // Maps to byte[]
struct Page{ int offset; ["java:buffer"] ByteSeq data; // Maps to java.nio.ByteBuffer}
interface Decoder{ ["java:buffer"] ByteSeq decode(ByteSeq data);}In this example, ByteSeq maps by default to a byte array, but we've overridden the mapping to use a buffer when this type is used as a field in Page and as the return value of the decode operation; the input parameter to decode uses the default array mapping.
Filling a Sequence of Bytes with a Serializable Object
In Java terminology, a serializable object typically refers to an object that implements the java.io.Serializable interface and therefore supports serialization to and from a byte stream. All Java classes generated from Slice definitions implement the java.io.Serializable interface.
In addition to serializing Slice types, applications may also need to incorporate foreign types into their Slice definitions. Ice allows you to pass Java serializable objects directly as operation parameters or as fields of another data type. For example:
["java:serializable:SomePackage.JavaClass"]sequence<byte> JavaObj;
struct MyStruct{ int i; JavaObj o;}
interface Example{ void op(JavaObj inObj, MyStruct s, out JavaObj outObj);}The generated code for MyStruct contains a member i of type int and a member o of type SomePackage.JavaClass:
public final class MyStruct implements java.lang.Cloneable{ public int i; public SomePackage.JavaClass o; // ...}Similarly, the signature for op has parameters of type JavaClass and MyStruct for the in-parameters and returns JavaClass:
SomePackage.JavaClass op(SomePackage.JavaClass inObj, MyStruct s);Of course, your client and server code must have an implementation of JavaClass that derives from java.io.Serializable:
package SomePackage;public class JavaClass implements java.io.Serializable{ // ...}You can implement this class in any way you see fit — the Ice runtime does not place any other requirements on the implementation.
Default Mapping
A Slice sequence maps to a JavaScript array.
- For JavaScript, the compiler does not generate a separate class or type.
- For TypeScript, it generates a type alias.
This allows you to take full advantage of the built-in functionality of JavaScript arrays.
For example:
sequence<Fruit> FruitPlatter;Generates the following TypeScript declaration:
export type FruitPlatter = Fruit[];Usage
// JavaScriptconst platter = [Fruit.Apple];platter.push(Fruit.Pear);// TypeScriptconst platter:FruitPlatter = [Fruit.Apple];platter.push(Fruit.Pear);Mapping for Byte Sequences
As an optimization, sequence<byte> maps to the JavaScript Uint8Array type. This representation is more efficient than regular arrays when working with binary data.
The MATLAB mapping for a Slice sequence depends on the element type of the sequence:
| Element Type (Slice) | Mapped Sequence Type (MATLAB) |
|---|---|
bool, numeric types, enum, struct | vector (1-by-n array) of the mapped element type |
string | vector of string |
All other types: class, proxies, dictionary, sequence | 1-by-n cell array of the mapped element type |
A Slice sequence maps to a native PHP indexed array. The first element of the Slice sequence is contained at index 0 (zero) of the PHP array, followed by the remaining elements in ascending index order.
Consider this example:
sequence<Fruit> FruitPlatter;You can create an instance of FruitPlatter as shown below:
// Make a small platter with one Apple and one Orange$platter = array(Fruit::Apple, Fruit::Orange);The Ice runtime validates the elements of an array to ensure that they are compatible with the declared type and throws InvalidArgumentException if an incompatible type is encountered.
Default Sequence Mapping
A Slice sequence maps to a native Python type:
- By default, sequences map to a list.
sequence<byte>maps to a bytes object, reducing memory usage and improving throughput.
Because native types are used, the Python mapping does not generate a separate named type for a Slice sequence. You can take advantage of all the functionality provided by Python’s built-in types.
For example:
sequence<Fruit> FruitPlatter;Usage in Python:
platter = [ Fruit.Apple, Fruit.Pear ]assert(len(platter) == 2)platter.append(Fruit.Orange)The Ice runtime validates the elements of a list (or tuple) to ensure they match the declared type. A ValueError is raised if an incompatible type is encountered.
Allowable Sequence Values
When you send a sequence value (for example, when calling a proxy method, or when returning a value or setting an output parameter in a servant method), you have flexibility:
- For all sequences, you can use any type that conforms to the Python
collections.abc.Sequenceabstract base class, provided its elements match the Python-mapped type of the Slice element. - For
sequence<byte>, in addition to a bytes object, you may also use any type that conforms toSequence[int].
Examples:
# Slice: sequence<int>ok1 = [1, 2, 3] # list[int]ok2 = (4, 5, 6) # tuple[int]bad = ["a", "b", "c"] # raises ValueError (wrong element type)
# Slice: sequence<byte>ok3 = bytes([1, 2, 3]) # efficient, recommendedok4 = [4, 5, 6] # list[int] also acceptedok5 = (7, 8, 9) # tuple[int] also acceptedFurthermore, the Ice runtime accepts any object that implements Python’s buffer protocol as a valid value for sequences of all primitive types (except strings).
For example, you can use the array module to create a buffer that is transferred more efficiently than a tuple or list:
import array...seq1 = array.array("i", [1, 2, 3, 4, 5])seq2 = [1, 2, 3, 4, 5]Both values have the same on-the-wire representation, but buffers incur much less marshaling overhead than lists or tuples.
Customizing the Sequence Mapping
When you receive a sequence (e.g., as a field value, a dispatch method parameter, or an invocation return/out parameter), the container is created by the Ice runtime.
By default:
- Most sequences are received as lists.
sequence<byte>is received as a bytes object.
You can change the container type used for received sequences by adding metadata to your Slice definitions.
Supported Metadata Directives
| Metadata | Description |
|---|---|
python:list | Map to a Python list. |
python:tuple | Map to a Python tuple. |
python:array.array | Map to a Python array.array (valid for integral types, excluding strings). |
python:numpy.ndarray | Map to a numpy.ndarray (valid for integral types, excluding strings). |
python:memoryview:<factory function>:<optional type hint> | Map to a custom Python type created from a memoryview using a factory function (valid for integral types, excluding strings). |
Metadata can be specified when defining a sequence, or at the point of use (parameter, return value, or field).
- At the definition site, it applies to all uses unless overridden.
- At the point of use, it overrides the default or definition-level mapping.
sequence<int> IntList; // Defaults to list["python:tuple"] sequence<int> IntTuple; // Defaults to tuple
sequence<byte> ByteString; // Defaults to bytes["python:list"] sequence<byte> ByteList; // Defaults to list
["python:array.array"] sequence<int> IntArray; // Defaults to array.array["python:numpy.ndarray"] sequence<long> LongArray; // Defaults to numpy.ndarray
struct S{ IntList i1; // list IntTuple i2; // tuple ["python:tuple"] IntList i3; // tuple ["python:list"] IntTuple i4; // list
ByteString b1; // bytes ByteList b2; // list ["python:list"] ByteString b3; // list ["python:tuple"] ByteString b4; // tuple}
interface I{ IntList op1(ByteString s1, out ByteList s2);
["python:tuple"] IntList op2( ["python:list"] ByteString s1, ["python:tuple"] out ByteList s2);}- The fields of S show how metadata can change the container type per field.
- The operation op2 shows how metadata applies differently for input parameters (server) and for return/out parameters (client).
While you can override the containers type at the point of use is typically more convenient to define different sequence types each with the desired metadata, and use them instead of specifying the metadata at the point of use.
Using python:memoryview
The python:memoryview directive provides maximum flexibility: you can supply a factory function that maps unmarshaled data to a custom sequence type.
For example, suppose your application uses NumPy arrays of numpy.complex128. You can define a sequence with:
["python:memoryview:Custom.myNumPyComplex128Seq:numpy.ndarray"]sequence<byte> Complex128Seq;
Complex128Seq opComplex();Factory function implementation:
def myNumPyComplex128Seq(buffer: memoryview | None, type: int) -> numpy.ndarray: if buffer is None: return numpy.empty(0, numpy.complex128) else: return numpy.frombuffer(buffer.tobytes(), numpy.complex128)- buffer: a memoryview containing the unmarshaled data.
- type: the element type (here
Ice.BuiltinBytebecause the sequence element isbyte).
Slice Element Type ↔ Python Constant
| Slice Element Type | Python Constant |
|---|---|
bool | Ice.BuiltinBool |
byte | Ice.BuiltinByte |
short | Ice.BuiltinShort |
int | Ice.BuiltinInt |
long | Ice.BuiltinLong |
float | Ice.BuiltinFloat |
double | Ice.BuiltinDouble |
Array Mapping
A Slice sequence maps to a Ruby array; the only exception is a sequence of bytes, which maps to a string. The use of a Ruby array means that the mapping does not generate a separate named type for a Slice sequence. It also means that you can take advantage of all the array functionality provided by Ruby. For example:
sequence<Fruit> FruitPlatter;We can use the FruitPlatter sequence as shown below:
platter = [ Fruit::Apple, Fruit::Pear ]platter.push(Fruit::Orange)The Ice runtime validates the elements of a sequence to ensure that they are compatible with the declared type; a TypeError exception is thrown if an incompatible type is encountered.
Mapping for Byte Sequences
A Ruby string can contain arbitrary 8-bit binary data, therefore it is a more efficient representation of a byte sequence than a Ruby array in both memory utilization and throughput performance.
When receiving a byte sequence (as the result of an operation, as an out parameter, or as a member of a data structure), the value is always represented as a string. When sending a byte sequence as an operation parameter or data member, the Ice runtime accepts both a string and an array of integers as legal values. For example, consider the following Slice definitions:
// Slicesequence<byte> Data;
interface I{ void sendData(Data d); Data getData();}The interpreter session below uses these Slice definitions to demonstrate the mapping for a sequence of bytes:
> proxy = ...> proxy.sendData("\0\1\2\3") # Send as a string> proxy.sendData([0, 1, 2, 3]) # Send as an array> d = proxy.getData()> d.class=> String> d=> "\000\001\002\003"The two invocations of sendData are equivalent; however, the second invocation incurs additional overhead as the Ice runtime must validate the type and range of each array element.
Here is the definition of our FruitPlatter sequence once more:
sequence<Fruit> FruitPlatter;The Slice compiler generates the following definition for the FruitPlatter sequence:
public typealias FruitPlatter = [Fruit]As you can see, the sequence simply maps to a standard array, so you can use the sequence like any other array. For example:
// Make a small platter with one Apple and one Orange//let platter: FruitPlatter = [.Apple, .Orange]There is a single exception to this mapping rule: a sequence of bytes maps to Foundation.Data and not [UInt8]:
sequence<byte> ByteSeq;becomes:
public typealias ByteSeq = Foundation.Data