Views and Backends¶
httk₂ separates what data is from how you want to work with it right now.
A backend carries one representation of the data; a view presents a
backend through the interface you need; an XLike union names everything a
function accepts. The implied contract: views are immutable access — one
piece of data, read through as many formats as you like, all consistent. The
moment you need to mutate, you leave the view world, and how you leave it
matters (see below).
Inside a function you normalize once to the view that suits your algorithm and write the logic against that view only:
import numpy
from httk.core import FracVector, coerce_view
from httk.core.vectors import VectorFracView, VectorLike
def center(vector: VectorLike) -> VectorLike:
view = VectorFracView(vector) # normalize once: exact arithmetic inside
result = view - view[0]
return coerce_view(result, vector) # answer in the caller's own kind
print(center(FracVector(["1/2", "2/3", "3/4"]))) # prints: (1/144)*(0, 24, 36)
print(center(numpy.array([0.5, 2 / 3, 0.75]))) # prints: [0. 0.16666667 0.25 ]
Four verbs cover every direction of movement:
unwrap(v)— recover the original backend object, never a copy;unview(v)— shed the httk wrapper and get a plain value of the presented type;coerce_view(v, t)— best-effort re-presentation ast, keeping the exact backend behind view results;coerce(v, t)— strict: a plain instance oftorTypeError.
Reading, mutating, and the cost of unview¶
Example 1 — immutable multi-format access, the normal case. Any number of views over one backend read consistently; none of them copies eagerly:
import numpy
from httk.core.vectors import VectorFracView, VectorNumpyView
array = numpy.array([[0.5, 0.0], [0.0, 0.25]])
view = VectorNumpyView(array) # zero-copy adoption
exact = VectorFracView(view) # same backend, exact presentation
assert exact[0, 0] == numpy.asarray(view)[0, 0] == 0.5
Example 2 — mutation through unview expires the views. unview removes
the httk wrapper; it does not detach. For the zero-copy numpy path the
result is the adopted storage:
from httk.core import unview
shed = unview(view)
assert shed is array # the very same ndarray — no copy was made
Mutating shed (or array) now conceptually expires every view aliasing
that storage — here both view and exact. There is no runtime tracking:
a sibling view that has not yet converted will see the new value, one that
already cached its presentation will not. Treat any read from an expired view
as undefined; mutate only after the views built on that data are out of use.
Example 3 — mutate a copy and keep the views valid. When you need the
data mutable and the views alive, copy through the target representation’s
own mechanism — this is the “unview but do not invalidate” pattern:
independent = numpy.array(unview(view), copy=True)
independent[0, 0] = 99.0 # views on the original stay valid
assert exact[0, 0] == 0.5
There is deliberately no generic unview_copy verb: what “copy” means (deep
or shallow, dtype, layout) belongs to the target representation’s own
vocabulary, and the presentations that are immutable by construction — native
nested tuples, Fraction leaves — carry no aliasing hazard in the first
place (unview of a native view returns a plain tuple; mutating means
building a list or array, which is already a copy). The hazard is
concentrated exactly where zero-copy sharing is: adopted and shed numpy
arrays.
For the full pattern — naming conventions, the verb semantics table, the borrowing lifetime contract, shared stream state, and design guidance for new domains — see Views and Backends in detail.