Skip to content

Equal

The Equal module provides a simple and convenient way to define and check for equality between two values in TypeScript.

Here are some key reasons why Effect exports an Equal module:

  1. Value-Based Equality: JavaScript’s native equality operators (=== and ==) check for equality by reference, meaning they compare objects based on their memory addresses rather than their content. This behavior can be problematic when you want to compare objects with the same values but different references. The Equal module offers a solution by allowing developers to define custom equality checks based on the values of objects.

  2. Custom Equality: The Equal module enables developers to implement custom equality checks for their data types and classes. This is crucial when you have specific requirements for determining when two objects should be considered equal. By implementing the Equal interface, developers can define their own equality logic.

  3. Data Integrity: In some applications, maintaining data integrity is crucial. The ability to perform value-based equality checks ensures that identical data is not duplicated within collections like sets or maps. This can lead to more efficient memory usage and more predictable behavior.

  4. Predictable Behavior: The Equal module promotes more predictable behavior when comparing objects. By explicitly defining equality criteria, developers can avoid unexpected results that may occur with JavaScript’s default reference-based equality checks.

In Effect it’s advisable to stop using JavaScript’s === and == operators and instead rely on the Equal.equals function. This function can work with any data type that implements the Equal interface. Some examples of such data types include Option, Either, HashSet, and HashMap.

When you use Equal.equals and your objects do not implement the Equal interface, it defaults to using the === operator for object comparison:

Example (Using Equal.equals with Default Comparison)

import {
import Equal
Equal
} from "effect"
// Two objects with identical properties and values
const
const a: {
name: string;
age: number;
}
a
= {
name: string
name
: "Alice",
age: number
age
: 30 }
const
const b: {
name: string;
age: number;
}
b
= {
name: string
name
: "Alice",
age: number
age
: 30 }
// Equal.equals falls back to the default '===' comparison
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import Equal
Equal
.
function equals<{
name: string;
age: number;
}, {
name: string;
age: number;
}>(self: {
name: string;
age: number;
}, that: {
name: string;
age: number;
}): boolean (+1 overload)

@since2.0.0

equals
(
const a: {
name: string;
age: number;
}
a
,
const b: {
name: string;
age: number;
}
b
))
// Output: false

In this example, a and b are two separate objects with the same contents. However, === considers them different because they occupy different memory locations. This behavior can lead to unexpected results when you want to compare values based on their content.

However, you can configure your models to ensure that Equal.equals behaves consistently with your custom equality checks. There are two alternative approaches:

  1. Implementing the Equal Interface: This method is useful when you need to define your custom equality check.

  2. Using the Data Module: For simple value equality, the Data module provides a more straightforward solution by automatically generating default implementations for Equal.

Let’s explore both.

To create custom equality behavior, you can implement the Equal interface in your models. This interface extends the Hash interface from the Hash module.

Example (Implementing Equal and Hash for a Custom Class)

import {
import Equal
Equal
,
import Hash
Hash
} from "effect"
class
class Person
Person
implements
import Equal
Equal
.
interface Equal

@since2.0.0

Equal
{
constructor(
readonly
Person.id: number
id
: number, // Unique identifier
readonly
Person.name: string
name
: string,
readonly
Person.age: number
age
: number
) {}
// Define equality based on id, name, and age
[
import Equal
Equal
.
const symbol: typeof Equal.symbol

@since2.0.0

symbol
](
that: Equal.Equal
that
:
import Equal
Equal
.
interface Equal

@since2.0.0

Equal
): boolean {
if (
that: Equal.Equal
that
instanceof
class Person
Person
) {
return (
import Equal
Equal
.
function equals<number, number>(self: number, that: number): boolean (+1 overload)

@since2.0.0

equals
(this.
Person.id: number
id
,
that: Person
that
.
Person.id: number
id
) &&
import Equal
Equal
.
function equals<string, string>(self: string, that: string): boolean (+1 overload)

@since2.0.0

equals
(this.
Person.name: string
name
,
that: Person
that
.
Person.name: string
name
) &&
import Equal
Equal
.
function equals<number, number>(self: number, that: number): boolean (+1 overload)

@since2.0.0

equals
(this.
Person.age: number
age
,
that: Person
that
.
Person.age: number
age
)
)
}
return false
}
// Generate a hash code based on the unique id
[
import Hash
Hash
.
const symbol: typeof Hash.symbol

@since2.0.0

symbol
](): number {
return
import Hash
Hash
.
const hash: <number>(self: number) => number

@since2.0.0

hash
(this.
Person.id: number
id
)
}
}

In the above code, we define a custom equality function [Equal.symbol] and a hash function [Hash.symbol] for the Person class. The Hash interface optimizes equality checks by comparing hash values instead of the objects themselves. When you use the Equal.equals function to compare two objects, it first checks if their hash values are equal. If not, it quickly determines that the objects are not equal, avoiding the need for a detailed property-by-property comparison.

Once you’ve implemented the Equal interface, you can utilize the Equal.equals function to check for equality using your custom logic.

Example (Comparing Person Instances)

import {
import Equal
Equal
,
import Hash
Hash
} from "effect"
24 collapsed lines
class
class Person
Person
implements
import Equal
Equal
.
interface Equal

@since2.0.0

Equal
{
constructor(
readonly
Person.id: number
id
: number, // Unique identifier for each person
readonly
Person.name: string
name
: string,
readonly
Person.age: number
age
: number
) {}
// Defines equality based on id, name, and age
[
import Equal
Equal
.
const symbol: typeof Equal.symbol

@since2.0.0

symbol
](
that: Equal.Equal
that
:
import Equal
Equal
.
interface Equal

@since2.0.0

Equal
): boolean {
if (
that: Equal.Equal
that
instanceof
class Person
Person
) {
return (
import Equal
Equal
.
function equals<number, number>(self: number, that: number): boolean (+1 overload)

@since2.0.0

equals
(this.
Person.id: number
id
,
that: Person
that
.
Person.id: number
id
) &&
import Equal
Equal
.
function equals<string, string>(self: string, that: string): boolean (+1 overload)

@since2.0.0

equals
(this.
Person.name: string
name
,
that: Person
that
.
Person.name: string
name
) &&
import Equal
Equal
.
function equals<number, number>(self: number, that: number): boolean (+1 overload)

@since2.0.0

equals
(this.
Person.age: number
age
,
that: Person
that
.
Person.age: number
age
)
)
}
return false
}
// Generates a hash code based primarily on the unique id
[
import Hash
Hash
.
const symbol: typeof Hash.symbol

@since2.0.0

symbol
](): number {
return
import Hash
Hash
.
const hash: <number>(self: number) => number

@since2.0.0

hash
(this.
Person.id: number
id
)
}
}
const
const alice: Person
alice
= new
constructor Person(id: number, name: string, age: number): Person
Person
(1, "Alice", 30)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import Equal
Equal
.
function equals<Person, Person>(self: Person, that: Person): boolean (+1 overload)

@since2.0.0

equals
(
const alice: Person
alice
, new
constructor Person(id: number, name: string, age: number): Person
Person
(1, "Alice", 30)))
// Output: true
const
const bob: Person
bob
= new
constructor Person(id: number, name: string, age: number): Person
Person
(2, "Bob", 40)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import Equal
Equal
.
function equals<Person, Person>(self: Person, that: Person): boolean (+1 overload)

@since2.0.0

equals
(
const alice: Person
alice
,
const bob: Person
bob
))
// Output: false

In this code, the equality check returns true when comparing alice to a new Person object with identical property values and false when comparing alice to bob due to their differing property values.

Implementing both Equal and Hash can become cumbersome when all you need is straightforward value equality checks. Luckily, the Data module provides a simpler solution. It offers APIs that automatically generate default implementations for both Equal and Hash.

Example (Using Data.struct for Equality Checks)

import {
import Equal
Equal
,
import Data
Data
} from "effect"
const
const alice: {
readonly id: number;
readonly name: string;
readonly age: number;
}
alice
=
import Data
Data
.
const struct: <{
id: number;
name: string;
age: number;
}>(a: {
id: number;
name: string;
age: number;
}) => {
readonly id: number;
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
id: number
id
: 1,
name: string
name
: "Alice",
age: number
age
: 30 })
const
const bob: {
readonly id: number;
readonly name: string;
readonly age: number;
}
bob
=
import Data
Data
.
const struct: <{
id: number;
name: string;
age: number;
}>(a: {
id: number;
name: string;
age: number;
}) => {
readonly id: number;
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
id: number
id
: 2,
name: string
name
: "Bob",
age: number
age
: 40 })
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import Equal
Equal
.
function equals<{
readonly id: number;
readonly name: string;
readonly age: number;
}, {
readonly id: number;
readonly name: string;
readonly age: number;
}>(self: {
readonly id: number;
readonly name: string;
readonly age: number;
}, that: {
readonly id: number;
readonly name: string;
readonly age: number;
}): boolean (+1 overload)

@since2.0.0

equals
(
const alice: {
readonly id: number;
readonly name: string;
readonly age: number;
}
alice
,
import Data
Data
.
const struct: <{
id: number;
name: string;
age: number;
}>(a: {
id: number;
name: string;
age: number;
}) => {
readonly id: number;
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
id: number
id
: 1,
name: string
name
: "Alice",
age: number
age
: 30 }))
)
// Output: true
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import Equal
Equal
.
function equals<{
readonly id: number;
readonly name: string;
readonly age: number;
}, {
id: number;
name: string;
age: number;
}>(self: {
readonly id: number;
readonly name: string;
readonly age: number;
}, that: {
id: number;
name: string;
age: number;
}): boolean (+1 overload)

@since2.0.0

equals
(
const alice: {
readonly id: number;
readonly name: string;
readonly age: number;
}
alice
, {
id: number
id
: 1,
name: string
name
: "Alice",
age: number
age
: 30 }))
// Output: false
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import Equal
Equal
.
function equals<{
readonly id: number;
readonly name: string;
readonly age: number;
}, {
readonly id: number;
readonly name: string;
readonly age: number;
}>(self: {
readonly id: number;
readonly name: string;
readonly age: number;
}, that: {
readonly id: number;
readonly name: string;
readonly age: number;
}): boolean (+1 overload)

@since2.0.0

equals
(
const alice: {
readonly id: number;
readonly name: string;
readonly age: number;
}
alice
,
const bob: {
readonly id: number;
readonly name: string;
readonly age: number;
}
bob
))
// Output: false

In this example, we use the Data.struct function to create structured data objects and check their equality using Equal.equals. The Data module simplifies the process by providing a default implementation for both Equal and Hash, allowing you to focus on comparing values without the need for explicit implementations.

The Data module isn’t limited to just structs. It can handle various data types, including tuples, arrays, and records. If you’re curious about how to leverage its full range of features, you can explore the Data module documentation.

JavaScript’s built-in Set and Map can be a bit tricky when it comes to checking equality:

Example (Native Set with Reference-Based Equality)

const
const set: Set<unknown>
set
= new
var Set: SetConstructor
new <unknown>(iterable?: Iterable<unknown> | null | undefined) => Set<unknown> (+1 overload)
Set
()
// Adding two objects with the same content to the set
const set: Set<unknown>
set
.
Set<unknown>.add(value: unknown): Set<unknown>

Appends a new element with a specified value to the end of the Set.

add
({
name: string
name
: "Alice",
age: number
age
: 30 })
const set: Set<unknown>
set
.
Set<unknown>.add(value: unknown): Set<unknown>

Appends a new element with a specified value to the end of the Set.

add
({
name: string
name
: "Alice",
age: number
age
: 30 })
// Even though the objects have identical values, they are treated
// as different elements because JavaScript compares objects by reference,
// not by value.
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
const set: Set<unknown>
set
.
Set<unknown>.size: number

@returnsthe number of (unique) elements in Set.

size
)
// Output: 2

Even though the two elements in the set have the same values, the set contains two elements. Why? JavaScript’s Set checks for equality by reference, not by values.

To perform value-based equality checks, you’ll need to use the Hash* collection types available in the effect package. These collection types, such as HashSet and HashMap, provide support for the Equal interface.

When you use the HashSet, it correctly handles value-based equality checks. In the following example, even though you’re adding two objects with the same values, the HashSet treats them as a single element.

Example (Using HashSet for Value-Based Equality)

import {
import HashSet
HashSet
,
import Data
Data
} from "effect"
// Creating a HashSet with objects that implement the Equal interface
const
const set: HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>
set
=
import HashSet
HashSet
.
const empty: <never>() => HashSet.HashSet<never>

Creates an empty HashSet.

@since2.0.0

empty
().
Pipeable.pipe<HashSet.HashSet<never>, HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>, HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>>(this: HashSet.HashSet<...>, ab: (_: HashSet.HashSet<...>) => HashSet.HashSet<...>, bc: (_: HashSet.HashSet<...>) => HashSet.HashSet<...>): HashSet.HashSet<...> (+21 overloads)
pipe
(
import HashSet
HashSet
.
const add: <{
readonly name: string;
readonly age: number;
}>(value: {
readonly name: string;
readonly age: number;
}) => (self: HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>) => HashSet.HashSet<...> (+1 overload)

Adds a value to the HashSet.

@since2.0.0

add
(
import Data
Data
.
const struct: <{
name: string;
age: number;
}>(a: {
name: string;
age: number;
}) => {
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
name: string
name
: "Alice",
age: number
age
: 30 })),
import HashSet
HashSet
.
const add: <{
readonly name: string;
readonly age: number;
}>(value: {
readonly name: string;
readonly age: number;
}) => (self: HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>) => HashSet.HashSet<...> (+1 overload)

Adds a value to the HashSet.

@since2.0.0

add
(
import Data
Data
.
const struct: <{
name: string;
age: number;
}>(a: {
name: string;
age: number;
}) => {
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
name: string
name
: "Alice",
age: number
age
: 30 }))
)
// HashSet recognizes them as equal, so only one element is stored
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import HashSet
HashSet
.
const size: <{
readonly name: string;
readonly age: number;
}>(self: HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>) => number

Calculates the number of values in the HashSet.

@since2.0.0

size
(
const set: HashSet.HashSet<{
readonly name: string;
readonly age: number;
}>
set
))
// Output: 1

Note: It’s crucial to use elements that implement the Equal interface, either by implementing custom equality checks or by using the Data module. This ensures proper functionality when working with HashSet. Without this, you’ll encounter the same behavior as the native Set data type:

Example (Reference-Based Equality in HashSet)

import {
import HashSet
HashSet
} from "effect"
// Creating a HashSet with objects that do NOT implement
// the Equal interface
const
const set: HashSet.HashSet<{
name: string;
age: number;
}>
set
=
import HashSet
HashSet
.
const empty: <never>() => HashSet.HashSet<never>

Creates an empty HashSet.

@since2.0.0

empty
().
Pipeable.pipe<HashSet.HashSet<never>, HashSet.HashSet<{
name: string;
age: number;
}>, HashSet.HashSet<{
name: string;
age: number;
}>>(this: HashSet.HashSet<...>, ab: (_: HashSet.HashSet<never>) => HashSet.HashSet<...>, bc: (_: HashSet.HashSet<...>) => HashSet.HashSet<...>): HashSet.HashSet<...> (+21 overloads)
pipe
(
import HashSet
HashSet
.
const add: <{
name: string;
age: number;
}>(value: {
name: string;
age: number;
}) => (self: HashSet.HashSet<{
name: string;
age: number;
}>) => HashSet.HashSet<{
name: string;
age: number;
}> (+1 overload)

Adds a value to the HashSet.

@since2.0.0

add
({
name: string
name
: "Alice",
age: number
age
: 30 }),
import HashSet
HashSet
.
const add: <{
name: string;
age: number;
}>(value: {
name: string;
age: number;
}) => (self: HashSet.HashSet<{
name: string;
age: number;
}>) => HashSet.HashSet<{
name: string;
age: number;
}> (+1 overload)

Adds a value to the HashSet.

@since2.0.0

add
({
name: string
name
: "Alice",
age: number
age
: 30 })
)
// Since these objects are compared by reference,
// HashSet considers them different
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import HashSet
HashSet
.
const size: <{
name: string;
age: number;
}>(self: HashSet.HashSet<{
name: string;
age: number;
}>) => number

Calculates the number of values in the HashSet.

@since2.0.0

size
(
const set: HashSet.HashSet<{
name: string;
age: number;
}>
set
))
// Output: 2

In this case, without using the Data module alongside HashSet, you’ll experience the same behavior as the native Set data type. The set contains two elements because it checks for equality by reference, not by values.

When working with the HashMap, you have the advantage of comparing keys by their values instead of their references. This is particularly helpful in scenarios where you want to associate values with keys based on their content.

Example (Value-Based Key Comparisons with HashMap)

import {
import HashMap
HashMap
,
import Data
Data
} from "effect"
// Adding two objects with identical values as keys
const
const map: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>
map
=
import HashMap
HashMap
.
const empty: <never, never>() => HashMap.HashMap<never, never>

Creates a new HashMap.

@since2.0.0

empty
().
Pipeable.pipe<HashMap.HashMap<never, never>, HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>, HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>>(this: HashMap.HashMap<...>, ab: (_: HashMap.HashMap<...>) => HashMap.HashMap<...>, bc: (_: HashMap.HashMap<...>) => HashMap.HashMap<...>): HashMap.HashMap<...> (+21 overloads)
pipe
(
import HashMap
HashMap
.
const set: <{
readonly name: string;
readonly age: number;
}, number>(key: {
readonly name: string;
readonly age: number;
}, value: number) => (self: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>) => HashMap.HashMap<...> (+1 overload)

Sets the specified key to the specified value using the internal hashing function.

@since2.0.0

set
(
import Data
Data
.
const struct: <{
name: string;
age: number;
}>(a: {
name: string;
age: number;
}) => {
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
name: string
name
: "Alice",
age: number
age
: 30 }), 1),
import HashMap
HashMap
.
const set: <{
readonly name: string;
readonly age: number;
}, number>(key: {
readonly name: string;
readonly age: number;
}, value: number) => (self: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>) => HashMap.HashMap<...> (+1 overload)

Sets the specified key to the specified value using the internal hashing function.

@since2.0.0

set
(
import Data
Data
.
const struct: <{
name: string;
age: number;
}>(a: {
name: string;
age: number;
}) => {
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
name: string
name
: "Alice",
age: number
age
: 30 }), 2)
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import HashMap
HashMap
.
const size: <{
readonly name: string;
readonly age: number;
}, number>(self: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>) => number

Returns the number of entries within the HashMap.

@since2.0.0

size
(
const map: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>
map
))
// Output: 1
// Retrieve the value associated with a key
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
import HashMap
HashMap
.
const get: <{
readonly name: string;
readonly age: number;
}, number, {
readonly name: string;
readonly age: number;
}>(self: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>, key: {
readonly name: string;
readonly age: number;
}) => Option<...> (+1 overload)

Safely lookup the value for the specified key in the HashMap using the internal hashing function.

@since2.0.0

get
(
const map: HashMap.HashMap<{
readonly name: string;
readonly age: number;
}, number>
map
,
import Data
Data
.
const struct: <{
name: string;
age: number;
}>(a: {
name: string;
age: number;
}) => {
readonly name: string;
readonly age: number;
}

@example

import { Data, Equal } from "effect"
const alice = Data.struct({ name: "Alice", age: 30 })
const bob = Data.struct({ name: "Bob", age: 40 })
assert.deepStrictEqual(Equal.equals(alice, alice), true)
assert.deepStrictEqual(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 })), true)
assert.deepStrictEqual(Equal.equals(alice, { name: "Alice", age: 30 }), false)
assert.deepStrictEqual(Equal.equals(alice, bob), false)

@since2.0.0

struct
({
name: string
name
: "Alice",
age: number
age
: 30 })))
/*
Output:
{ _id: 'Option', _tag: 'Some', value: 2 }
*/

In this code snippet, HashMap is used to create a map where the keys are objects constructed with Data.struct. These objects contain identical values, which would usually create separate entries in a regular JavaScript Map because the default comparison is reference-based.

HashMap, however, uses value-based comparison, meaning the two objects with identical content are treated as the same key. Thus, when we add both objects, the second key-value pair overrides the first, resulting in a single entry in the map.