Collections

Viper C++ provides strongly-typed collections that mirror Python’s built-in collections while enforcing type safety.

Vector

ValueVector is compatible with Python’s list API:

>>> from dsviper import *

>>> v = Value.create(TypeVector(Type.STRING), ["hello", "world"])
>>> v
['hello', 'world']

Append a single element:

>>> v.append("!")
>>> v
['hello', 'world', '!']

Extend with a Python list:

>>> v.extend(["from", "python"])
>>> v
['hello', 'world', '!', 'from', 'python']

Index access (positive and negative):

>>> v[0]
'hello'
>>> v[-1]
'python'

Length:

>>> len(v)
5

Iterate:

>>> for item in v:
...     print(item)
hello
world
!
from
python

Note

ValueVector does not currently support slice access (v[1:3]). Iterate explicitly or use list(v) to materialize a slice in Python.

Type Safety

>>> v.append(42)
Traceback (most recent call last):
    ...
dsviper.ViperError: ...expected type 'str', got 'int'...

Set

ValueSet is compatible with Python’s set API:

>>> s = Value.create(TypeSet(Type.INT64), {1, 2, 3, 2, 1})
>>> s
{1, 2, 3}

Add and remove:

>>> s.add(4)
>>> s.remove(1)
>>> s
{2, 3, 4}

Membership and length:

>>> 2 in s
True
>>> len(s)
3

Set operations:

>>> s2 = Value.create(TypeSet(Type.INT64), {3, 4, 5})
>>> s.union(s2)
{2, 3, 4, 5}
>>> s.intersection(s2)
{3, 4}

Map

ValueMap is compatible with Python’s dict API:

>>> m = Value.create(TypeMap(Type.STRING, Type.INT64), {"a": 1, "b": 2})
>>> m
{'a': 1, 'b': 2}

Get and set:

>>> m["c"] = 3
>>> m["a"]
1

Keys, values, items:

>>> list(m.keys())
['a', 'b', 'c']
>>> list(m.values())
[1, 2, 3]
>>> list(m.items())
[('a', 1), ('b', 2), ('c', 3)]

Delete and length:

>>> del m["a"]
>>> len(m)
2

Complex Keys

Maps can use complex types as keys:

>>> t = TypeMap(TypeTuple([Type.INT64, Type.INT64]), Type.STRING)
>>> m = Value.create(t, {(1, 2): "one-two", (3, 4): "three-four"})
>>> m[(1, 2)]
'one-two'

Optional

ValueOptional holds a value or nothing:

>>> opt = Value.create(TypeOptional(Type.STRING))
>>> opt
nil

>>> opt.is_nil()
True

Wrap a value:

>>> opt.wrap("hello")
>>> opt
Optional('hello')

>>> opt.is_nil()
False

Unwrap:

>>> opt.unwrap()
'hello'

Unwrapping an empty optional raises an error:

>>> empty = Value.create(TypeOptional(Type.STRING))
>>> empty.unwrap()
Traceback (most recent call last):
    ...
dsviper.ViperError: ...Try to unwrap empty optional<string>...

Initialize with Value

>>> opt = Value.create(TypeOptional(Type.INT64), 42)
>>> opt
Optional(42)

Tuple

ValueTuple holds heterogeneous values. Note that booleans render with Viper C++’s own repr (true/false), not Python’s True/False:

>>> t = TypeTuple([Type.STRING, Type.INT64, Type.BOOL])
>>> v = Value.create(t, ("hello", 42, True))
>>> v
('hello', 42, true)

Access by index:

>>> v[0]
'hello'
>>> v[1]
42

Variant

ValueVariant holds one value from a set of possible types:

>>> t = TypeVariant([Type.STRING, Type.INT64])
>>> v = Value.create(t, "a string")
>>> v.type()
string|int64

Change the value:

>>> v.wrap(42)
>>> v.unwrap()
42

Wrong type is rejected:

>>> v.wrap(3.14)
Traceback (most recent call last):
    ...
dsviper.ViperError: ...expected type 'string|int64', got 'float'...

XArray

Vector vs XArray: When to Use Each

Feature

Vector

XArray

Indexing

Integer indices (0, 1, 2…)

UUID positions

Insert/Remove

Indices shift

Positions stable

Concurrent editing

Last-write-wins

Inserts preserved; edits LWW

Performance

Faster for local use

Slight overhead

Use case

Local arrays, batch processing

Concurrently edited lists

Why XArray? Under concurrent editing, two authors might insert at “index 3” simultaneously. With Vector, one insert wins and the other is lost or corrupted. With XArray, each element has a UUID position that remains stable across reduction — concurrent edits to the same element still collapse to last-write-wins.

Example: A shared todo list where multiple users add/remove items should use XArray. A local computation buffer should use Vector.

XArray Basics

ValueXArray addresses elements by stable UUID positions instead of integer indices, so concurrent inserts and removes neither collide nor shift one another — no edit is lost. It does not make the order of concurrently inserted elements deterministic: where two streams insert independently, their relative order is settled by reduction (fusion order), not preserved.

Create and append. Note that append() returns x.END — the zero-UUID sentinel that means “the end of the array” — not the position of the just-appended element:

>>> t = TypeXArray(Type.STRING)
>>> x = Value.create(t)

>>> x.END
00000000-0000-0000-0000-000000000000

>>> x.append("first")
00000000-0000-0000-0000-000000000000
>>> x.append("second")
00000000-0000-0000-0000-000000000000
>>> x
['first', 'second']

Recover the actual UUID position of an element with position(index) or position_of(value), then access it with at(pos) (or x[pos]):

>>> pos0 = x.position(0)
>>> x.at(pos0)
'first'
>>> x[pos0]
'first'

>>> p = x.position_of("second")
>>> x.at(p)
'second'

insert(pos, value) inserts before the given position and returns the UUID position of the new element. Inserting at x.END is therefore equivalent to appending; inserting at pos0 prepends:

>>> p_third = x.insert(x.END, "third")
>>> x.at(p_third)
'third'

>>> p_zero = x.insert(pos0, "zero")
>>> x.at(p_zero)
'zero'

>>> x
['zero', 'first', 'second', 'third']

Iterate over values, or over (position, value) pairs:

>>> for item in x:
...     print(item)
zero
first
second
third

Any

TypeAny accepts any value:

>>> v = Value.create(TypeVector(Type.ANY))
>>> v.append("a string")
>>> v.append(42)
>>> v.append([1, 2, 3])

>>> v.description()
"['a string':string:any, 42:int64:any, [1, 2, 3]:vector<int64>:any]:vector<any>"

Nested Collections

Collections can be nested:

>>> t = TypeVector(TypeMap(Type.STRING, Type.INT64))
>>> v = Value.create(t)
>>> v.append({"a": 1})
>>> v.append({"b": 2, "c": 3})
>>> v
[{'a': 1}, {'b': 2, 'c': 3}]