Unit
Unit type for void-like operations
The Unit type is a functional programming alternative to void. Unlike void, Unit is a proper type that can be used as a value.
What is Unit?
Unit is just a type alias for void:
type Unit = void;It comes with a single value:
const unit: Unit = undefined;Why Use Unit?
In JavaScript/TypeScript, void has a limitation - it's not a proper value:
// ❌ This doesn't work
function returnsVoid(): void {
return undefined; // OK
}
const value = returnsVoid();
// value is void, can't do much with itUnit solves this by providing a proper value:
import { unit } from '@deessejs/functions';
function returnsUnit(): Unit {
return unit; // OK
}
const value = returnsUnit();
// value is Unit (which is void), but you have a valueWhen to Use Unit?
Functions That Don't Return Values
Use Unit for functions that perform side effects but don't return meaningful data:
import { unit } from '@deessejs/functions';
function log(message: string): Unit {
console.log(message);
return unit;
}
function saveUser(user: User): Unit {
db.save(user);
return unit;
}Result Types for Void Operations
Use Unit with Result for operations that can fail but don't return data on success:
import { success, failure, unit } from '@deessejs/functions';
function deleteUser(id: string): Result<Unit, Error> {
const user = db.find(id);
if (!user) {
return failure(new Error("User not found"));
}
db.delete(id);
return success(unit);
}
// Usage
const result = deleteUser("123");
result.match({
onSuccess: (value) => {
// value is Unit
console.log("User deleted successfully");
},
onFailure: (error) => {
console.error("Error:", error.message);
},
});Async Operations
async function sendEmail(email: string): AsyncResult<Unit, Error> {
try {
await mailer.send({ to: email, subject: "Hello" });
return success(unit);
} catch (error) {
return failure(error as Error);
}
}Outcomes
import { successOutcome, failureOutcome, unit } from '@deessejs/functions';
function logActivity(userId: string): Outcome<Unit, Cause, Exception> {
try {
db.activities.insert({ userId, timestamp: Date.now() });
return successOutcome(unit);
} catch (error) {
return exceptionOutcome(exceptionFromError(error as Error));
}
}Type Signatures
type Unit = void;
const unit: Unit = undefined;Common Patterns
Command Pattern
type Command<T> = (input: T) => Unit;
const logUser: Command<User> = (user) => {
console.log(`User: ${user.name}`);
};
const saveUser: Command<User> = (user) => {
db.save(user);
};
// Execute commands
[logUser, saveUser].forEach((cmd) => cmd(user));Pipeline with Side Effects
import { pipe } from '@deessejs/functions';
function validate(data: unknown): Result<User, Error> {
// ...
}
function sanitize(user: User): User {
return { ...user, name: user.name.trim() };
}
function save(user: User): Unit {
db.save(user);
return unit;
}
// Pipeline
const result = pipe(
validate(userData),
(user) => success(sanitize(user)),
(user) => success(save(user))
);Void Callbacks
import { unit } from '@deessejs/functions';
function withLogging<T>(fn: () => T): T {
console.log("Starting...");
const result = fn();
console.log("Done");
return result;
}
// Usage
withLogging(() => {
console.log("Processing...");
return unit;
});Best Practices
1. Use Unit for Side Effects
// ✅ Good - clear intent
function sendNotification(user: User): Unit {
notifier.send(user.email);
return unit;
}
// ❌ Confusing - returns undefined
function sendNotification(user: User): undefined {
notifier.send(user.email);
return undefined;
}2. Use with Result for Better Type Safety
// ✅ Good - explicit success/failure
function deleteUser(id: string): Result<Unit, Error> {
if (!db.exists(id)) {
return failure(new Error("User not found"));
}
db.delete(id);
return success(unit);
}
// ⚠️ Less clear - boolean
function deleteUser(id: string): boolean {
if (!db.exists(id)) {
return false;
}
db.delete(id);
return true;
}3. Chain Unit Operations
// ✅ Good - chaining with Result
async function processUser(id: string): AsyncResult<Unit, Error> {
const user = await fetchUser(id);
if (user.isFailure()) return failure(user.error);
const validated = validateUser(user.value);
if (validated.isFailure()) return failure(validated.error);
const saved = saveUser(validated.value);
if (saved.isFailure()) return failure(saved.error);
return success(unit);
}Unit vs Void
TypeScript void
function returnsVoid(): void {
console.log("Side effect");
// No return needed
}
const value = returnsVoid();
// Can't use value for anythingUnit
import { unit } from '@deessejs/functions';
function returnsUnit(): Unit {
console.log("Side effect");
return unit;
}
const value = returnsUnit();
// value is Unit, can be used in generic contextsWhen NOT to Use Unit
Don't Use When You Have Meaningful Data
// ❌ Bad - throwing away useful data
function getUser(id: string): Result<Unit, Error> {
const user = db.find(id);
if (!user) return failure(new Error("Not found"));
return success(unit); // Where's the user?!
}
// ✅ Good - return the user
function getUser(id: string): Result<User, Error> {
const user = db.find(id);
if (!user) return failure(new Error("Not found"));
return success(user);
}Don't Use for Boolean Returns
// ❌ Bad - using Unit for success/failure
function hasPermission(user: User): Result<Unit, Error> {
if (!user.isAdmin) {
return failure(new Error("Unauthorized"));
}
return success(unit);
}
// ✅ Good - use boolean or Maybe
function hasPermission(user: User): boolean {
return user.isAdmin;
}
function hasPermission(user: User): Maybe<Permission> {
return user.permission ? some(user.permission) : none();
}Real-World Examples
API Operations
import { success, failure, unit } from '@deessejs/functions';
async function updateUser(
id: string,
updates: Partial<User>
): AsyncResult<Unit, Error> {
const user = await db.users.find(id);
if (!user) {
return failure(new Error("User not found"));
}
await db.users.update(id, { ...user, ...updates });
return success(unit);
}Event Handlers
import { unit } from '@deessejs/functions';
function handleClick(event: MouseEvent): Unit {
console.log("Clicked at:", event.x, event.y);
trackEvent("click", { x: event.x, y: event.y });
return unit;
}Data Processing Pipeline
function processData(data: Data[]): Result<Unit, Error> {
const validated = validateAll(data);
if (validated.isFailure()) return failure(validated.error);
const transformed = transformAll(validated.value);
if (transformed.isFailure()) return failure(transformed.error);
const saved = saveAll(transformed.value);
if (saved.isFailure()) return failure(saved.error);
return success(unit);
}See Also
- Result - Success/failure type often used with Unit
- Async Result - Async version of Result
- Outcome - Advanced result system