Python and TypeScript

The Python package and the TypeScript package are one design in two idioms. Both are thin views over the same runtime: a generated object holds one Viper value and delegates every operation to it, so what an operation does is the runtime’s in both languages. What differs is how each language spells it. The two surfaces stay parallel: a feature added to one is added to the other in the same change.

Both follow the C++ surface, the pack’s base reference: they offer what it offers, with its restrictions (see kibo-template-viper).

Where the idioms differ

Concern

Python

TypeScript

Viper value

unwrap_value() over _value; S.wrap_value(value)

unwrapValue(); S.wrapValue(value)

construct

S(value | dict | None, **fields) (a value is boxed), SKey(uuid | str)

new S(value | record?) (a value is boxed), new SKey(uuid?)

structure field

@property + setter

get/set accessors

enumeration

enum.Enum, from_str, index via E(i)

literal union + object: fromStr, index, unwrapValue

container

Sequence / Mapping / Ordered, dunder protocol

same views, Symbol.iterator + methods

absent optional

None

undefined

equality, hash, order, display

==, hash(), <, repr()

equals, hashKey, compare, toString / toJSON

container protocol

len(), in, contains, empty, to_list / to_tuple, items, v + w, m[c] = column

size / length, has, toArray, entries, concat, setColumn

a value of another type

TypeError, from every wrap_value and constructor

TypeError, from every wrapValue and constructor

key conversion

to_parent_key(), to_<child>_key(), from_<member>_key(), to_<member>_key(), to_any_concept_key(), from_any_concept_key()

toParentKey(), to<Child>Key(), from<Member>Key(), to<Member>Key(), toAnyConceptKey(), fromAnyConceptKey()

an unknown case name (from_str / fromStr)

ValueError

dsviper.ViperError

a variant read as an alternative it does not hold (get_A() / getA())

ValueError

TypeError

a native number stored in an any

an int is an integer (Any(42), an int64), a float a double

a number is a double (Any(42.0)); a bigint is an int64 (new AnyValue(42n) is Any(42))

documentation

the DSM documentation on the class, the field, the attachment

the same places; a structure’s, on the class, not on its …Init

attachment

<unit>.attachments.<Concept>.<attachment>.get(getting, key)

<unit>.attachments.<Concept>.<attachment>.get(getting, key); <Concept> is also exported by the unit’s attachments module

attachment verbs

keys, has, get, enumerate, diff_keys, set, delete, diff (del is a keyword)

keys, has, get, enumerate, diffKeys, set, del, diff, as the C++

field-level verbs

set_<f>, union_<f>, subtract_<f>, update_<f>, insert_<f>, remove_<f>

set<F>, union<F>, subtract<F>, update<F>, insert<F>, remove<F>

function pool

Pool (local, holding NAME and UUID) and Remote

Remote; NAME and UUID are the module’s

The argument order is the same everywhere: state first, then key, then value.

Why the rows are idioms

Each row is the same meaning written in each language’s grammar, never a different meaning. Python spells a field in snake_case and TypeScript keeps the model’s name; Python has None where TypeScript has undefined; Python’s del is a keyword, so an attachment deletes with delete there and with del in TypeScript, as in C++; Python reaches a container through its dunder protocol (len(), in, +), TypeScript through Symbol.iterator and methods; Python hashes with hash(), TypeScript gives hashKey(), the key a native Map or Set needs.

The error rows follow the three layers both pages describe — an argument of another kind, content the runtime refuses, a read — and each language’s own name for the last one. A case name is a lookup: Python’s enum answers a failed lookup with ValueError, while TypeScript has no such convention and passes on the runtime’s ViperError. A variant read as the wrong alternative is a read of a value of another type: Python names it ValueError, as it does for a value it cannot take, and TypeScript TypeError. The any row is each language’s number type: Python’s int and float are two types, and the runtime keeps the difference; a JavaScript number is always a double, and an integer reaches an any as a bigint, as int64 fields take it.

The line between idiom and drift is the runtime. A difference that lives in the static surface and follows the host language — an enum.Enum against a literal union, del against delete — is legitimate. A difference that changes what happens at run time — another operation, another argument order, a value that is not the runtime’s — is a bug, whatever the language. Divergences beyond the rows above are drift, not idiom.

One row records a gap rather than an idiom: a function pool. Python renders a local Pool beside Remote; TypeScript renders the client side, Remote, only.

How the parity is checked

The rows were measured, not recalled. The pack’s test laboratory, devkit-codegen-test, renders the Python and TypeScript packages of each of its sites and lists every public member of both — the classes and their members, an attachment’s operations, the containers, the runtime the package re-exports. It compares them by name, case and underscores aside (diff_keys is diffKeys, f_e is f_E), together with whether each member carries documentation.

A difference is either one of the idioms above, which the laboratory holds as data with its reason, or drift: a member one language has and the other lacks, or documents and the other does not. Drift fails the laboratory’s check (check.py, through tools/parity.py). An idiom is added there only together with its row in the table above.