Beitrag vom: 06.12.2024 (updated: 18.12.2024 07:34) I will teach you about how useful immutability is and how to leverage it in typescript (but similar concepts apply to other languages as well). Basically the name describes it already: Making stuff immutable: Not changeable. In typescript all basic datatypes are immutable! So when you do: Just as a hint to future content: immutability is a major part in functional programming ;). To dive deeper into (im)mutability let's start with an example: In the top part the stuff inside the variable changes and makes it harder to predict whats inside it. "But what about counter variables? I NEED to count stuff all the time!?" Correct. Let's break this up a little: The guideline is: When I say "seldom" I mean, that in many cases Type/Javascript will give us a way to avoid counter variables: Obviously this example is trivial, but in any way: We KNOW, that Immutability doesn't mean all the way and also doesn't mean that mutable stuff is inherently bad, but it definetely removes some foodguns, when programming and debugging. Biome has a lint Imagine the following: We expect Functions which don't mutate parameters and are solely depending on parameters are called pure functions. A concept which has various advantages over unpure functions. But I'll go into that in the next article. Right. They are. Especially because of javascript, but other languages like python/php/etc. have similar problems. Fortunately with typescript we have powerful tools at our hands: I personally prefer: And then use the result: I think this is better as ittl;dr
What is immutability?
let a = 5;
let b = a;
a = 123;
// 5 still exists in memory
b = 42;
// 5 will be garbage collected soon
let helloWorldString = "Hello World!"; // immutable
helloWorldString = "Hello Moon!"; // no error, but var name no longer correct!
// vs:
const helloWorldString = "Hello World!"; // immutable
helloWorldString = "Hello Moon!"; // error! We can rely on the content of the var!
let uppercaseNames = [];
for (let name of ["alice", "bob", "charlie"]) {
uppercaseNames.push(name.toUpperCase());
}
// Functional approach
const uppercaseNames = ["alice", "bob", "charlie"].map((name) =>
name.toUpperCase(),
);
uppercaseNames
will contain exactly what it tells us, as soon as it's defined!Why
lint/style/useConst
forcing us to use const
when a variable is never reassigned, but I'd go further and replace let
s as often as possible by const
.const mutateVar = (innerA) => {
innerA = 9;
return 12;
};
let outerA = 123;
const result = mutateVar(outerA);
// no change due to pass-by-value for elemental types
console.log({ result, outerA });
// But:
const mutateObj = (obj) => {
// sideeffect we don't know about when reading function name:
obj.x = 123;
return obj;
};
const originalObject = { a: 42 };
const mutatedObject = mutateObj(originalObject);
console.log({ originalObject, mutatedObject });
originalObject
to be still the same and mutatedObject
to contain the new value. If we try to remember to not change existing objects and also use const
we will never be surprised by behaviour inside a function.But const objects ARE immutable
Readonly
, readonly
and as const
.// Readonly
type Tree = { name: string };
const ahorn: Readonly<Tree> = { name: "ahorn" };
ahorn.name = "bhorn"; // typescript error
// As const
const ahorn = { name: "ahorn" } as const;
ahorn.name = "bhorn"; // typescript error
// readonly
class Tree {
constructor(readonly name: string) {}
}
const t = new Tree("ahorn");
t.name = "asdf"; // typescript error
What about string builders
/** Approach of most people */
function greetCustomer(customer) {
let message = "";
if (customer.age > 15) {
message += "Good day sir,\n";
} else {
message += "Hey little one,\n";
}
if (customer.hasEaten) {
message += "Can I offer you something to drink?";
} else {
message += "Can I offer you our lunch menu?";
}
console.log(message);
}
// vs what I prefer
function buildGreeting(customer): string {
const greet = customer.age > 15 ? "Good day sir" : "Hey little one";
const offer = customer.hasEaten ? "something to drink" : "our lunch menu";
return `${greet},\nCan I offer you ${offer}?`;
}
function greetCustomer(customer) {
const greeting = buildGreeting(customer);
console.log(greeting);
}
buildGreeting
function easier to test as we don't rely on catching console output