Types and Values¶
The Viper C++ type system provides strong typing with runtime validation. This chapter covers how to create values from Node and how the type system surfaces through the binding. The Python equivalent is Types and Values; the type system is identical — only the idiom differs.
Examples assume the binding is in scope:
const { Value, Type, ValueBool, ValueInt8, ValueInt32, ValueDouble,
ValueString, ValueUUId, TypeVector } = require('@digitalsubstrate/dsviper');
The universal constructor¶
Create any value with Value.create(type, [initialValue]):
Value.create(Type.DOUBLE).encoded(); // 0.0
Value.create(Type.STRING, 'hello').encoded(); // 'hello'
Value.create(new TypeVector(Type.INT64), [1, 2, 3]).size(); // 3
If initialValue is omitted, the value is initialized to the type’s “zero”.
encoded() returns the underlying JavaScript native; representation() returns
the canonical string form.
Choosing the right constructor¶
Pattern |
When to use |
Example |
|---|---|---|
|
Primitive type constant |
|
|
Parameterized container |
|
|
Direct value (known type) |
|
|
Universal factory |
|
|
Let Viper C++ infer the type |
|
|
Factory with generation |
|
Quick rules:
Use
Type.Xfor primitives:Type.STRING,Type.INT64,Type.BOOL.Use
new TypeX(...)for containers:TypeVector,TypeMap,TypeOptional.Use
new ValueX(...)when the 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 the native).
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 |
A type knows its own name and rendering:
Type.DOUBLE.typeName().name(); // 'double'
Type.DOUBLE.description(); // 'double'
Primitive types¶
Boolean¶
Viper values render with their own representation — booleans are true/false:
new ValueBool(false).representation(); // 'false'
new ValueBool(true).representation(); // 'true'
Type checking is enforced — a wrong native throws:
try {
new ValueBool(5); // an integer is not a bool
} catch (e) {
// a ViperError is thrown
}
Integers¶
Signed and unsigned integers with range validation. INT64/UINT64 round-trip
through bigint:
Value.create(Type.INT8, 42).encoded(); // 42
Value.create(Type.INT64, 42n).encoded(); // 42n (bigint)
try {
new ValueInt8(128); // out of range (INT8 max is 127)
} catch (e) {
// a ViperError is thrown
}
Available types: INT8, INT16, INT32, INT64, UINT8, UINT16,
UINT32, UINT64.
Floats¶
new ValueDouble(3.14159).representation(); // '3.14159'
ValueDouble.ZERO.encoded(); // 0.0
ValueDouble.tryParse('1.23e-4').encoded(); // 0.000123
ValueDouble.tryParse('not a number'); // undefined
ValueDouble follows IEEE 754 with Viper’s total order: NaN equals NaN
(and hashes equally), and sorts before every other value.
String¶
Value.create(Type.STRING).encoded(); // ''
new ValueString('hello world').encoded(); // 'hello world'
UUID¶
ValueUUId.create() generates a fresh UUID; pass a string to parse a known one:
const u = ValueUUId.create();
u.representation(); // e.g. 'c98ddeec-0496-494c-b1e9-b470ff204be3'
ValueUUId.create('c98ddeec-0496-494c-b1e9-b470ff204be3'); // parsed
try {
new ValueUUId('not-a-uuid');
} catch (e) {
// a ViperError is thrown
}
Type deduction¶
Let Viper infer the type from a native with Value.deduce():
const v = Value.deduce(3.14);
v instanceof ValueDouble; // true
v.type().equals(Type.DOUBLE); // true
Type information¶
Every value knows its type:
const v = new ValueDouble(1.618);
v.typeCode(); // 'double'
v.type().equals(Type.DOUBLE); // true
v.description(); // contains both the value and 'double'
Type checking and the seamless bridge¶
The bridge uses type metadata to validate and convert natives — there is
nothing to register. A value of the expected type is accepted; a cross-type
cast throws a ViperError:
ValueDouble.cast(new ValueDouble(3.14)); // ok
try {
ValueDouble.cast(new ValueInt32(42)); // wrong type
} catch (e) {
e.name; // 'ViperError'
}
Value semantics¶
equals, hash, and compare are the value contract (see
dsviper for Node.js for the full Python ↔ Node mapping). They accept a native argument
directly:
new ValueDouble(2).equals(2); // true
new ValueDouble(1).compare(2) < 0; // true
new ValueDouble(2).equals('foo'); // false (incompatible → false, not a throw)
Remember that JavaScript == between two values is reference equality, not
value equality — reach for .equals.