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¶
Structures and Enumerations - User-defined types
Database - Persisting collections