question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Better Distinctions of Builder Immutablility

See original GitHub issue

Which package is the feature request for?

discord.js

Feature

Builders does it’s job adequately for what it’s meant for, however there are some issues when repurposing it for discord.js. Functionally it operates the same, however the way it’s returned can be confusing to say the least.

Builders imply mutability regardless of where they’re used

A good example of this is embeds from messages. The setX methods, data are all fully public and exposed. Even when the data shouldn’t be mutable (because it’s from the API).

Another example is unintentionally inaccurate types. Let’s take SelectMenuInteraction for example, when you receive a select menu interaction we can get the component like so:

const { component } = selectMenuInteraction;

Ok great, nothing out of the ordinary, now let’s check the type of the customId:

const id: string = component.customId // Error: string | undefined is not assignable to string

Wait what? How is customId null-able. After all, you can’t send in a select menu without a custom id? This is because the types here are representative of a class with mutable/changing state. In the context of a builder string | undefined is a perfectly acceptable type since the structure hasn’t been fully built.

Yes, toJSON indicates a finalized state, but only if you’re using raw API data. In discord.js this isn’t the case, so there should be new structures to hold immutable api data.

Ideal solution or implementation

Introduce immutable (Built) classes

Many of the most widely used patterns for builders include a build(). This method simply signals that the builder is complete, and its state will always remain immutable via a new class.

For example:


class Car {
	public readonly wheels: Wheels;
	public readonly windows?: Windows;
	public readonly seats: Seats;

	constructor(data: ...) {
		this.wheels = data.wheels;
		this.windows = data.windows;
		this.seats = data.seats;
	}

	public drive() {
		console.log('vroom!');
	}
}

class CarBuilder {
	public wheels?: Wheels;
	public windows?: Windows;
	public seats?: Seats;

	setWheels(wheels: Wheels) { ... }
	setWindows(windows: Windows) { ... }
	setSeats(seats: Seats) { ... }

	build() {
		// Validate required inputs (we can use toJSON for this)
		return new Car({ ...this });
	}
}
const car: Car = new CarBuilder()
.setWheels(new Wheels())
.setSeats(new Seats())
.build();

car.drive(); // Prints "vroom!"

Ideally this could be applied within discord.js so there’s a better and safer distinction to what is already built and what is currently a builder.

Note: the pattern above is a common OOP builder pattern. See these resources for references:

Alternative solutions or implementations

No response

Other context

No response

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:4
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
suneettipirnenicommented, Feb 24, 2022

So I’ve been thinking this through and there’s one more thing that should change to allow this.

Remove getters from builders and relocate them to built structures

This sounds controversial at first but hear me out.

Builders are a means to building a finalized structure, they aren’t the structures themselves. When structures are used/consumed usually it’s via getters/props. Builders having getters for props doesn’t make sense if a finalized version of it is added. In addition, moving getters from a builder to the built structure presents less confusion about the purpose of a built structure vs a builder (one is for making a structure, one is the built structure that can be passed around. It prevents people from trying to repurpose mutable builders as structures and gives incentive to finalize them via a build() method.

Note: Builders would still expose their data field for access to properties, however this would ideally only be used for situations where the library can’t handle something that you need.

This would solve 3 issues:

  • We don’t need worry about possibly maintaining two sets of getters for each structure, since only the finalized (built) structures have getters.
  • Getter types will always be accurate, since we guarantee that a built builder will have required fields present. We don’t have to represent two different field types for every structure. (example: SelectMenuComponent#customId)
  • If we plan to make builders for some djs structure like Message in the future, djs can easily extend the finalized version of a message builder rather than try to override types and methods to make it work in djs. (Trying to extend a mutable message builder in djs would not be fun)

Along with this structures would be renamed to align with the changes for example SelectMenuComponent -> SelectMenuBuilder

Then,

SelectMenuBuilder#build returns a finalized SelectMenuComponent

1reaction
suneettipirnenicommented, Feb 22, 2022

If anyone has any ideas on how to implement this without duplicating all the getters from builders I’m all ears. Having a hard time coming up with an implementation 😅

Read more comments on GitHub >

github_iconTop Results From Across the Web

Reading 9: Mutability & Immutability - MIT
Getting good performance is one reason why we use mutable objects. Another is convenient sharing: two parts of your program can communicate more...
Read more >
To build or not to build - refactoring towards immutability
Immutability in itself is widely considered good. First of all, it avoids side effects, and thus makes the code and handling it safer....
Read more >
Mutable vs Immutable Java | Top 6 Differences in ... - eduCBA
Guide to the top difference between Mutable vs Immutable Java. Here we also discuss key differences with infographics and comparison tables.
Read more >
The Builder Pattern (using immutability) - YouTube
Your browser can't play this video. Learn more. Switch camera.
Read more >
What is the difference between a mutable and immutable ...
Immutable string is a String in C#. A new memory is created every time. A String cannot be changed once it is created,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found