Description
Increasing access
This proposal would make p5.js more beginner friendly, in three stages:
- Establish consensus on a set of criteria and guidelines for the API.
- Resolve complications in the current API.
- Implement a simple process for preventing complications in the future.
Which types of changes would be made?
- Breaking change (Add-on libraries or sketches will work differently even if their code stays the same.)
- Systemic change (Many features or contributor workflows will be affected.)
- Overdue change (Modifications will be made that have been desirable for a long time.)
- Unsure (The community can help to determine the type of change.)
What's the problem?
The p5.js API contains quite a few inconsistencies and other forms of complexity. This complexity makes p5.js harder to learn and harder to use, since users experience it directly through its API. While some parts of the API have been simplified for 2.0, such as the API for vertex functions, other complexities remain. This is because identifying and preventing complexities in a large API is difficult without clear guidelines and a practical quality assurance process.
What's the solution?
1. Establish criteria and guidelines
In order to identify and prevent unnecessary complexity in the p5 API, we can develop a list of criteria we want it to satisfy. We can also provide guidelines for common types of features, such as getters and setters. Initial criteria and guidelines are listed below.
Criteria
- Consistency: Provide a similar interface for similar features and different interfaces for different features.
- Readability: Design features that are easy to read and understand.
- Writability: Make features easy to use and hard to misuse.
- Predictability: Respect the principle of least surprise, so that users can guess how features work.
- Extensibility: Hide implementation details and lay a foundation for add-on libraries.
- Economy: Limit redundant features, and consider add-on libraries for new features.
For a given feature, these design criteria may be considered with respect to naming, syntax, and semantics.
Guidelines
- Getters and setters:
- A function or method with the same name as a setting, such as
stroke()
andfill()
, should act jointly as a getter (when no argument is passed) and a setter. - General setters such as
vertexProperty(name, value)
should act as getters whenvalue
is omitted. - For consistency with commonly used functions such as
stroke()
, joint getters/setters should be used when possible. However, if separate getters and setters are to be used, they should begin with “get” and “set,” except for getters for Booleans, which should have names such asisLooping()
. - Something about setting fields directly? (Guidance might prevent inconsistencies like using the field
disableFriendlyErrors
to toggle friendly errors and using functions likefullscreen()
andautoSized()
to toggle other settings.)
- A function or method with the same name as a setting, such as
- Other sets of guidelines...
During the second phase, when specific kinds of issues are dealt with, the guidelines can be fleshed out.
References:
- “How to Design a Good API & Why it Matters” by Joshua Bloch
- The Little Manual of API Design
- API Design Matters
- API Guidelines in the Wild
2. Perform an audit (a scavenger hunt!)
We could hold a scavenger hunt for features that don't adhere to our API criteria. Beginners could make significant contributions by spotting naming inconsistencies, for example. To get the ball rolling, I performed a rapid audit of the full API. The methodology and a partial sample of results are described below.
Methodology
I skimmed through the whole reference and looked for issues that jumped out to me as potential areas of concern (I didn’t systematically apply all criteria to each feature). I also skipped over some things that I didn’t suspect were problematic.
Caveats:
- It's possible that some of these issues have already been addressed in the development version of p5.js 2.0.
- I skipped the vertex functions since we already came up with a new vertex API.
- I skipped over the parts of the API that are documented as experimental, such as
baseColorShader()
. - I skipped the following features since they might get pruned:
p5.Table
andp5.TableRow
- Array Functions (except for
shuffle()
) p5.NumberDict
,p5.StringDict
, andp5.TypedDict
Results (partial sample)
For illustration, only a partial sample of results from the rapid audit are included here. Skimming this section should give a rough sense of what's at stake.
Note: The sample includes examples of potential problems with naming, syntax, and semantics (broadly defined). Each example is tagged with the criteria that fail to be satisfied.
This partial sample of API problems can be replaced with separate sub-issues, with one issue for each affected section of the reference.
Note: The sample only includes issues that may require breaking changes, but the audit did reveal other issues that could be fixed with non-breaking changes after the release of p5.js 2.0.
Naming
color()
- Relevant criteria: Consistency, predictability
- Problem: Unlike other constructor functions in p5, this function does not start with a “create” prefix, leading users to guess that it’s a setter such as
stroke()
. - Potential fix: Replace
color()
withcreateColor()
.
getTargetFrameRate()
/frameRate()
- Relevant criteria: Consistency, predictability
- Problem: In this pair of functions, different names are used for the same value. To get the target frame rate, users call
getTargetFrameRate()
, but to set the target frame rate, they useframeRate()
. Also, unlike a joint getter and setter such aspixelDensity()
,frameRate()
gets a different value than it sets (it sets the target frame rate but gets the calculated frame rate). - Potential fix: Replace these features with
idealFrameRate()
(gets and sets the ideal frame rate) andcalculateFrameRate()
(calculates the actual frame rate, similar tocalculateBoundingBox()
). An alternative is to replace "ideal" with "target"; however, whentargetFrameRate()
is compared withcalculateFrameRate()
, "target" could be interpreted as a verb instead of an adjective, which may cause confusion.
setAttributes()
- Relevant criteria: Consistency, readability, predictability
- Problem: This feature has a couple of issues. First, it has a plural name, but
setAttributes(key, value)
only sets a single attribute. Second, the name is ambiguous, since it only sets attributes of the WebGL drawing context, but this isn't advertised in the name. - Potential fix: Both of these issues could potentially be fixed by replacing
setAttributes()
with, say,canvasProperty(key, value)
.
onended()
- Relevant criteria: Consistency, readability
- Problem: This feature’s name doesn’t use camel case, the name is awkward linguistically, and it’s inconsistent with similar methods of p5.Element, like
touchEnded()
. - Potential fix:: Replace
onended()
withplayEnded()
. (However, there is a deeper issue, which is that these functions take a callback function, whereas event handlers likedoubleClicked()
are defined directly by the user. If one of these two behaviors cannot be used in all cases, then they should be distinguished by their names. For example, a name likeonTouchEnd()
could be used for one type of behavior and a name liketouchEnded()
could be used for another.)
Syntax
createModel()
,loadModel()
- Relevant criteria: Consistency, readability, writability
- Problem: The same parameters in these functions appear in different orders (
normalize
andfileType
are out of order in one signature,fileType
is out of order in another, andfileType
is missing from one signature). - Potential fix: One option is to make the parameter lists consistent. Another is to choose a different API that allows the parameters to be specified in any order (and there are potentially multiple such APIs).
spotLight()
,directionalLight()
andpointLight()
- Relevant criteria: Readability, economy
- Problem: As an example,
spotLight()
has eight signatures. It accepts three vectors as input, but in some signatures, some of these vectors are specified asp5.Vector
objects whereas others are not. This makes the documentation unnecessarily complicated and leads to code that’s hard to read. The other listed features have the same type of issue. - Potential fix: A potential fix is to remove the signatures that mix componentwise specifications and
p5.Vector
specifications. The pure componentwise signature ofspotLight()
has 11 parameters and is very hard to read, but keeping both the pure componentwise and purep5.Vector
signatures reduces breaking changes and keeps these features consistent with other functions that take multiple vectors as input, such asquad()
. (Sincequad()
takes up to a whopping 14 parameters, and sincepoint()
already accepts both componentwise andp5.Vector
input, it makes sense to add support toquad()
and all other 2D primitive functions for both types of input.)
- p5.Graphics, p5.Framebuffer
- Relevant criteria: Consistency, predictability
- Problem: The reference says these classes are “similar,” and it’s true. Semantically, they are similar. However, syntactically, they’re significantly different. Requiring users to learn different interfaces for the same kinds of features reduces accessibility. If these aren’t reconciled, complications are likely to compound (e.g. the
p5.Shape
class developed for 2.0 has an interface that’s closer to that ofp5.Graphics
, and it may be exposed to users in a future version). - Potential fix: Requires discussion.
Semantics
addClass()
,class()
- Relevant criteria: Readability, economy
- Problem: The
p5.Element
class hasaddClass()
andclass()
, both of which add a class to the element. The difference is thatclass()
is a joint getter/setter that seems to makeaddClass()
unnecessary. Having both is likely to confuse users. - Potential fix: Remove
addClass()
.
millis()
- Relevant criteria: Consistency, readability, predictability
- Problem: Unlike all other time and date functions,
millis()
returns the time since the sketch started running; all other functions, includingsecond()
, return the current time (e.g. if a sketch is run 32 seconds after noon, then when the sketch starts running,second()
will return 32 whereasmillis()
will return 0). Also, “millis” is an abbreviation, unlike the other function names; that doesn’t seem like enough to convey the difference in behavior. - Potential fix: Requires discussion.
debugMode()
- Relevant criteria: Readability, writability, extensibility
- Problem: This function has multiple signatures, which take up to nine numerical parameters. Extending this feature is desirable but impractical, given that it’s already complex. Also, “mode” is a problematic description of this feature, since it actually provides different helpers (a grid helper and an axes helper) which can be combined, rather than modes, which are typically mutually exclusive.
- Potential fix: The three.js library provides a GridHelper, an AxesHelper, and a variety of other helpers (a camera helper, a spotlight helper, etc.). In p5, having separate
axisHelper()
andgridHelper()
functions should be an improvement. We might also consider functions likeaxesHelperProperty(key, value)
for greater readability and writability (effectively allowing named parameters).
- 2D Primitives
- Relevant criteria: Consistency, predictability
- Problem: Whereas
quad()
has multiple signatures and can create a shape in 2D or 3D,triangle()
only works in 2D. Other issues include shapes that can be drawn in WebGL mode but only by specifying xy-coordinates rather than xyz-coordinates. Some of these shapes support a detail parameter while others don’t. Some supportp5.Vector
arguments and others don’t. Rounding corners inrect()
andsquare()
adds another complication. - Potential fix: The first thing is to make a table with one row per feature, and columns for dimensions, vector arguments, and so on. This will help us identify the exact issues that need to be addressed, and which of them would require breaking changes to fix (@davepagurek and I mostly finished the table).
3. Implement quality assurance process via issue templates
Bugs can be fixed in any version of p5. API problems cannot be fixed outside of a major version release (if at all), if they're like any of the problems listed above. To avoid introducing such problems in the future, we can implement a basic quality assurance process via the issue templates for new features and existing feature enhancements:
- Include a section for code examples. The community can help with filling this in, so that early-stage ideas are not discouraged.
- Include a checklist, to be completed before releasing the feature and closing the issue. The checklist can indicate criteria and guidelines. For each checklist item, “good” and “bad” examples can be provided (either directly on the issue template, or via a link to the contributor docs).
- Indicate what to expect after submitting a new-feature request:
a. Documentation will be required before release (and encouraged before implementation).
b. Review will be required by multiple stakeholders.
Volunteers
There are various ways volunteers might help! Specific guidance can be provided once separate sub-issues have been created for the various parts of this proposal. For now, here are some examples of work that would need to be done:
- Create a complete list of all abbreviations in the p5.js API
- Create a complete list of all getters and setters
- Submit problems using the same format as above (relevant criteria, problem, potential fix)
- Implement changes in the source code (further in the future)
If you're interested in helping with this issue, you can leave a comment, and I can add your username here. Once the sub-issues have been created, I'll add a comment on this issue and tag everyone listed to let them know.
@dhruvinjs, @ImRAJAS-SAMSE, @Vaivaswat2244, @Jai2082001
Pros (updated based on community comments)
- Consistency: Inconsistencies will be fixed or prevented.
- Readability: Confusion about the meaning of code will be reduced or prevented.
- Writability: Features will be easier to use and harder to misuse.
- Predictability: Unpleasant surprises will be eliminated or prevented.
- Extensibility: Features will be more extensible, both for the core p5 library and for add-ons.
- Economy: Confusion or cognitive overload from redundant features will be reduced or prevented.
Cons (updated based on community comments)
- Breaking changes: Existing code may break. (However, we can reduce the impact in various ways. For name changes, we could implement the new names as aliases, mark the old features as
@deprecated
if they're scheduled for removal, and then move them into a Deprecation module. Deprecated features would then be listed in a dedicated section in the reference. For changes to syntax and semantics, we could develop a compatibility add-on.) - Time expenditure: Resolving this issue will take time. (However, we can speed it up by inviting more contributors. This issue is largely beginner friendly, as noted above in the Volunteers section.)
Proposal status
Under review