Skip to content

IVectorN.Length() returns bare T, dropping the dimension at the one point the caller wants it #238

Description

@matt-edmondson

CLAUDE.md states: "IVectorN.Magnitude() (for N >= 1) returns the corresponding IVector0." The C++ projection does exactly that — each form answers magnitude() with the magnitude form of the same dimension, and the doc makes a point of how nicely the dimension works out through sqrt.

The C# side does not. IVector3<TVector, T> declares:

public T Length();
public T Distance(TVector other);

So position.Length() on a Position3D<T> hands back a bare T. The dimension is discarded precisely where the caller wanted Length<T> — or Distance<T> — and has to be re-wrapped by hand, which is the untyped step the library exists to remove. The same applies to IVector2 and IVector4.

Checked across all 72 entries in dimensions.json: every dimension that declares a vector form also declares a vector0, so there is no case where the magnitude type is unavailable.

Design

Add alongside the existing members without changing them — Length() and Distance() stay, since generated code and consumers use them:

/// <summary>Gets the magnitude of this vector as its dimension's magnitude quantity.</summary>
public global::ktsu.Semantics.Quantities.Length<T> Magnitude()
    => global::ktsu.Semantics.Quantities.Length<T>.Create(StorageMath.Sqrt(LengthSquared()));

/// <summary>Gets the distance to another vector as its dimension's magnitude quantity.</summary>
public global::ktsu.Semantics.Quantities.Length<T> DistanceTo(Position3D<T> other)
    => global::ktsu.Semantics.Quantities.Length<T>.Create(StorageMath.Sqrt(DistanceSquared(other)));

Four things to settle

1. Return the V0 base, not an overload. Displacement3D<T>.Magnitude() returns Length<T>, not Distance<T>, because the base is what every overload widens from (resolved decision 3 in CLAUDE.md). A caller who wants Distance narrows explicitly. That also keeps the generator rule purely mechanical: read quantities.vector0.base.

2. A name collision the generator has to handle. Displacement3D<T> already has a method called Length(), so writing Length<T> as a return type inside that same class puts a method group and a generic type under one identifier. It parses — the type argument list disambiguates — but it is fragile and unreadable, and it will confuse anyone reading the generated source. Emit the fully qualified name, as above. This is the one non-obvious implementation detail in the issue.

3. Do not emit MagnitudeSquared(). C++ answers it with a bare Quantity<D> for the honest reason that the square of a dimension usually has no name, and where it has one it is not unique. C# has no structural layer to fall back on, and Velocity² has no declared name at all — so there is nothing to return. Leave LengthSquared() returning T, which is the correct answer rather than a missing feature.

4. Magnitude() uses Create, not a From{Unit} factory, so it bypasses Vector0Guards. That is correct — a magnitude is non-negative by construction — and it avoids paying for a guard on every call in a hot loop.

Tests

  • For each vector form, Magnitude() equals the V0 constructed from Length()
  • The return type is the dimension's V0 base — a compile-time check, so an explicitly typed local is enough
  • Magnitude() of a zero vector equals the V0's Zero
  • DistanceTo agrees with Magnitude() of the difference, for the signed forms where subtraction is defined
  • Cover at least one dimension whose V0 base name differs from the dimension name (Velocity3DSpeed, Acceleration3DAccelerationMagnitude), since that is where a naive generator rule using the dimension name instead of the V0 base would break

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions