-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Description
[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 theset()
method. Whileset()
is more powerful (accepting multiple overloads), its overlap withvalues
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 withgetElements()
andsetElements()
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
- 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. - There is perhaps a very slight inconsistency with
x
,y
, andz
, 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 possiblyw
).
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
Type
Projects
Status