Skip to content

[2.0] Proposal: A clearer API for vector components (no breaking changes) #8152

@GregStanton

Description

@GregStanton

[2.0] Proposal: A clearer API for vector components (no breaking changes, minimal effort)

The current 2.0 API for getting and setting vector components contains ambiguity and complexity that may cause user confusion. It also sets a difficult precedent for future classes like Series and Sheet (replacements for the deprecated TableRow and Table), as well as Matrix and potentially Tensor. This proposal outlines a complete solution that is clearer and more consistent. It can also be implemented without breaking changes and without major effort.

Current 2.0 API Proposed API Breaking?
values getElements() / setElements() No, values hasn’t appeared in the reference yet.
getValue() / setValue() getElement() / setElement() No, we can deprecate getValue() / setValue().
set() (deprecated, replaced by setElements()) No, this is a deprecation.

The problem with the current API

The unreleased values property is the most significant issue, but set() also creates redundancy.

  • Ambiguous naming: The name values is vague. It could refer to all properties of the vector, not just its components. Standard terms like "element" are more precise.
  • Ambiguous behavior: Because values is intended to replace the getter array(), users may reasonably assume it's read-only, but it's actually writable. This is a recipe for confusion. (I actually made this mistake, as a Math steward.)
  • Confusing redundancy: The API has two ways to set all components: the values property and the set() method. While set() is more powerful (accepting multiple overloads), its overlap with values creates confusion.

The proposed solution and implementation

This proposal solves all identified problems by replacing the current API with a clear, consistent, and extensible alternative.

Recommended API:
getElement()/getElements(), setElement()/setElements()

This singular-plural pattern aligns with carefully designed p5.js features like splineProperty()/splineProperties(), providing a predictable experience for users.

The implementation can be broken into three simple, non-breaking tasks:

  • Replace values: The unreleased values property can be directly replaced with getElements() and setElements() methods. The internal logic (get/set keywords on a private _values array) can be reused, making this a minimal change.
  • Deprecate getValue()/setValue(): These can be deprecated in favor of their new names, getElement()/setElement(). Since they are new and unstable, removing them in 3.0 will cause near-zero disruption.
  • Deprecate set(): This method’s functionality is completely and more clearly covered by the new setElements() method.
A couple design details
  1. Although "entry" was originally proposed, "element" has only two extra characters and has a couple advantages. It's easier for non-English speakers to pluralize, and it doesn't conflict with the meaning of "entries" in the native array's entries() method.
  2. There is perhaps a very slight inconsistency with x, y, and z, since these are implemented as joint getters/setters, unlike the separate explicit getters and setters of the proposed API for general components. However, from a user perspective, these are simply numerical fields, which distinguishes them from the methods described in this proposal. Also, these fields are already special cases, as no other components are named (except possibly w).
Why getters/setters in p5 are better separate as class methods but joint as standalone functions

Purpose: This section proposes a long-term API pattern for getters and setters in p5. In the context of the current issue, it’s meant to validate the separate, explicit getter/setter pattern that’s currently used in p5.Vector for individual vector components. It also provides one reason to change from values, which has not yet appeared in the reference, to a separate getter and setter.

Since consistency is important, it's helpful to briefly consider the wider context of p5. The most basic features in p5 are standalone functions like stroke() and fill(). These functions do not have "get" or "set" prefixes. That’s likely beneficial, since a term like "set" feels more like a computer instruction, and it's important to use familiar vocabulary at this critical, early stage of learning to code. In any case, these features are in extremely wide use, and are among the first features that most users will learn, so they establish a very strong precedent.

At the same time, p5 often uses explicit “get” and “set” prefixes elsewhere in its API, especially in class methods. Currently, there isn’t an obvious, universal pattern for when these prefixes are used. These sorts of inconsistencies are a form of technical debt that tends to compound, so it's worthwhile to get the problem under control whenever we do a major release. In the long run, there seem to be a couple approaches for dealing with such inconsistencies that might be viable. Here, "long run" includes future releases such as 3.0.

Approach 1: Joint getters/setters everywhere
Here the idea is to move toward converting all getters and setters to joint getters/setters, with the generic API template propertyName(). There may be exceptions, like when a property is meant to be read only, but this is the overall idea.

Approach 2: Joint getters/setters in standalone functions only
Here the idea is to make all standalone functions like fill() into joint getters/setters, and to make all class-based getters and setters explicit, with "get" and "set" prefixes. Again, there may be special cases.

Weighing the approaches
Each of these options has merits. Converting everything to joint getters/setters provides a completely uniform interface and an economical API. On the other hand, joint getters/setters like fill() are ambiguously named, compared to explicitly named, dedicated getters and setters. Determining a path forward requires prioritizing these trade-offs and accounting for the disruption of breaking changes.

There's a reasonable case to be made that we should move toward the direction of Approach 2, so that joint getters/setters are used for standalone functions only. If all relevant standalone features are joint getters/setters and all relevant class-based methods are separate, explicit getters and setters, then that should provide enough consistency for users to quickly learn the API.

That narrows down the decision to economy vs. clarity. While there are a lot of getters and setters, the total number of them will be multiplied by at most a factor of two, and in reality the growth factor is likely much smaller (separate getters and setters are often used already). Since economy is not likely to be a dealbreaker, clarity should likely be prioritized for a library like p5. And by the time users graduate to object-oriented programming, they're ready for longer, clearer names like getHeading().

Most importantly, a quick CTRL+F of the main reference page suggests that Approach 2 would require significantly fewer breaking changes.

The conclusion for p5.Vector is that separate, explicitly named getters and setters are likely to be preferable.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions