Types and Values¶
The Viper C++ type system provides strong typing with runtime validation. This chapter covers all available types and how to create values.
The Universal Constructor¶
Create any value using Value.create(type, [initial_value]):
>>> from dsviper import *
>>> Value.create(TypeFloat())
0.0
>>> Value.create(Type.STRING, "hello")
'hello'
>>> Value.create(TypeVector(Type.INT64), [1, 2, 3])
[1, 2, 3]
If initial_value is omitted, the value is initialized to the type’s “zero”.
Choosing the Right Constructor¶
Viper C++ offers multiple ways to create values. Use this guide to pick the right one:
Pattern |
When to Use |
Example |
|---|---|---|
|
Primitive type constant |
|
|
Parameterized container |
|
|
Direct value (known type) |
|
|
Universal factory |
|
|
Let Viper C++ infer type |
|
|
Factory with generation |
|
Quick rules:
Use Type.X for primitives:
Type.STRING,Type.INT64,Type.BOOLUse TypeX(…) for containers:
TypeVector(...),TypeMap(...),TypeOptional(...)Use ValueX(…) when type is known and you want direct construction
Use Value.create(t, v) when working with dynamic types or DSM definitions
Use Value.deduce(v) for quick prototyping (type inferred from Python value)
Type Constants¶
Common types are available as constants on Type:
Constant |
Type |
|---|---|
|
Boolean |
|
Signed integers |
|
Unsigned integers |
|
32-bit float |
|
64-bit double |
|
UTF-8 string |
|
UUID |
|
Any type |
Primitive Types¶
Boolean¶
Note that Viper C++ values render with their own repr — booleans appear as
true/false, not Python’s True/False.
>>> Value.create(Type.BOOL)
false
>>> Value.create(Type.BOOL, True)
true
Type checking is enforced:
>>> Value.create(Type.BOOL, 4)
Traceback (most recent call last):
...
dsviper.ViperError: ...expected type 'bool', got 'int'...
Integers¶
Signed and unsigned integers with range validation:
>>> Value.create(Type.INT8)
0
>>> Value.create(Type.INT8, 42)
42
>>> Value.create(Type.INT8, 256)
Traceback (most recent call last):
...
dsviper.ViperError: ...value is not in the range of 'int8'...
Available types: INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64
Floats¶
>>> Value.create(Type.FLOAT)
0.0
>>> Value.create(Type.FLOAT, 42)
42.0
>>> Value.create(Type.FLOAT, 1e300)
Traceback (most recent call last):
...
dsviper.ViperError: ...value is not in the range of 'float'...
String¶
UTF-8 encoded strings:
>>> Value.create(Type.STRING)
''
>>> Value.create(Type.STRING, "hello world")
'hello world'
UUID¶
Generate a fresh UUID — ValueUUId.create() returns a randomly-generated value:
>>> uuid = ValueUUId.create()
>>> isinstance(str(uuid), str) and str(uuid).count('-') == 4
True
Parse a known UUID string:
>>> uuid = ValueUUId.create("c98ddeec-0496-494c-b1e9-b470ff204be3")
>>> uuid
c98ddeec-0496-494c-b1e9-b470ff204be3
Malformed UUIDs are rejected:
>>> ValueUUId.create("invalid")
Traceback (most recent call last):
...
dsviper.ViperError: ...malformed UUId...
Mathematical Types¶
Vec¶
Fixed-size numeric arrays:
>>> t = TypeVec(Type.FLOAT, 3)
>>> Value.create(t)
(0.0, 0.0, 0.0)
>>> Value.create(t, (1, 2, 3))
(1.0, 2.0, 3.0)
Mat¶
Matrices with columns and rows, stored column-major: the outer sequence lists the
columns, so mat<float, 2, 3> is two columns of three. m[column] is a column and
m[column, row] an element — read row-major, as numpy does by default, the matrix
comes out transposed.
>>> t = TypeMat(Type.FLOAT, 2, 3)
>>> Value.create(t)
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
>>> m = Value.create(t, ((1, 2, 3), (4, 5, 6)))
>>> m
[(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)]
>>> m[1]
(4.0, 5.0, 6.0)
>>> m[1, 2]
6.0
Type Deduction¶
Let Python infer the Viper C++ type with Value.deduce():
>>> v = Value.deduce([1, 2, 3])
>>> v.type()
vector<int64>
>>> v = Value.deduce([1, 2, (3, "string"), {1.0, 2.0}])
>>> v.type()
vector<int64|set<double>|tuple<int64, string>>
Type Information¶
Every value knows its type:
>>> v = Value.create(TypeVector(Type.STRING), ["a", "b"])
>>> v.type()
vector<string>
>>> v.description()
"['a', 'b']:vector<string>"
Type Checking¶
Viper C++ validates types at runtime:
>>> v = Value.create(TypeVector(Type.STRING))
>>> v.append("valid")
>>> v.append(123)
Traceback (most recent call last):
...
dsviper.ViperError: ...expected type 'str', got 'int'...
The Seamless Bridge¶
Python natives are automatically converted:
>>> v = Value.create(TypeVector(Type.STRING))
>>> v.extend(["from", "python", "list"])
>>> v
['from', 'python', 'list']
>>> m = Value.create(TypeMap(Type.STRING, Type.INT64), {"a": 1, "b": 2})
>>> m
{'a': 1, 'b': 2}
The bridge uses type metadata to validate and convert Python objects.
Reading Back: Natives or Values¶
The bridge works the other way too: reading a primitive hands back a Python native.
>>> v = Value.create(TypeVector(Type.INT8), [5, 6])
>>> v[0]
5
>>> type(v[0])
<class 'int'>
That projection keeps the number and drops the Viper type: int8, uint64 and every other
integer width read as int, float and double as float, and nothing recovers which one it
was. When the code needs the value itself — its type, to describe it, to copy it into another
value — the reads that project take encoded=False:
>>> x = v.at(0, encoded=False)
>>> type(x)
<class 'dsviper.ValueInt8'>
>>> x.type()
int8
A value that is not a primitive — a vector, an optional, a structure — comes back as a Value either way.
With a Type Checker¶
dsviper ships its type hints, so mypy and Pyright check your calls against the binding. A read
that projects is declared as the union of what it can answer: a native, or one of the Value
classes the projection leaves alone. Calling a method that only one of them has asks the code to
say which — and one line does:
>>> o = Value.create(TypeVector(TypeOptional(Type.STRING)), [None])
>>> ValueOptional.cast(o[0]).is_nil()
True
The class is known from context — the type you decoded with, the field you read: wrap the read in
ValueX.cast(...). It also checks at runtime, so a wrong class raises instead of travelling on.The code branches on the class:
isinstance(read, ValueX).The read may be absent —
CommitStore.state(),Codec.query(name), the declarations typedX | None: guard it, or state it withassert x is not None.
encoded=False answers a Value: enough for type(), representation() or Value.copy(),
not for a method of one Value class — that still takes a cast.