Collections

Viper 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’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

Multiplayer editing

Last-write-wins

Merge-friendly

Performance

Faster for local use

Slight overhead

Use case

Local arrays, batch processing

Shared editable lists

Why XArray? In multiplayer scenarios, two users 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 merges.

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

XArray Basics

ValueXArray preserves order during concurrent mutations using UUID positions instead of integer indices.

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}]

What’s Next