Functions

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 it

Unit 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 value

When 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 anything

Unit

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 contexts

When 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

On this page