## Example
```typescript before
/** @derive(PartialEq, Hash) */
class User {
id: number;
name: string;
@partialEq(skip) // Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
}
```
```typescript after
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
Generated output:
```typescript before
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
```typescript after
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
Generated output:
```typescript before
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
```typescript after
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
Generated output:
```typescript before
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
```typescript after
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
Generated output:
```typescript before
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
```typescript after
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
Generated output:
```typescript before
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
```typescript after
class User {
id: number;
name: string;
// Don't compare cached values
/** @hash({ skip: true }) */
cachedScore: number;
equals(other: unknown): boolean {
if (this === other) return true;
if (!(other instanceof User)) return false;
const typedOther = other as User;
return this.id === typedOther.id && this.name === typedOther.name;
}
hashCode(): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(this.id)
? this.id | 0
: this.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
hash =
(hash * 31 +
(Number.isInteger(this.cachedScore)
? this.cachedScore | 0
: this.cachedScore
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
return hash;
}
}
```
## Equality Contract
When implementing `PartialEq`, consider also implementing `Hash`:
- **Reflexivity**: `a.equals(a)` is always true
- **Symmetry**: `a.equals(b)` implies `b.equals(a)`
- **Hash consistency**: Equal objects must have equal hash codes