---
name: effect-typeclass-design
description: Implement typeclasses with curried signatures and dual APIs for both data-first and data-last usage
---

# Typeclass Design Skill

Use this skill when implementing typeclasses that provide reusable abstractions across multiple types.

## Pattern: Curried Typeclass Functions

All typeclass functions must be **fully curried** to enable partial application:

```typescript
import { Duration } from 'effect';

declare interface Durable<A> {
	readonly getDuration: (self: A) => Duration.Duration;
}

// Create a typeclass function that takes the typeclass instance first,
// then curries out all other parameters
export const isMoreThan =
	<A>(D: Durable<A>) =>
	(minimum: Duration.Duration) =>
	(self: A): boolean => {
		const current = D.getDuration(self);
		return Duration.isGreaterThanOrEqualTo(current, minimum);
	};
```

## Pattern: Dual APIs

Provide both **data-first** (uncurried) and **data-last** (curried) variants using `Function.dual`:

```typescript
import { Duration } from 'effect';
import * as Function from 'effect/Function';

declare interface Durable<A> {
	readonly getDuration: (self: A) => Duration.Duration;
}

export const isMoreThan = <A>(D: Durable<A>) =>
	Function.dual<
		// Data-last (curried) - pipe-friendly
		(minimum: Duration.Duration) => (self: A) => boolean,
		// Data-first (uncurried) - direct call
		(self: A, minimum: Duration.Duration) => boolean
	>(
		2, // Number of arguments for data-first form
		(self: A, minimum: Duration.Duration): boolean => {
			const current = D.getDuration(self);
			return Duration.isGreaterThanOrEqualTo(current, minimum);
		}
	);
```

## Usage Patterns

The dual API enables both styles:

```typescript
import { pipe } from 'effect/Function';
import * as Duration from 'effect/Duration';
import * as Function from 'effect/Function';

declare interface Durable<A> {
	readonly getDuration: (self: A) => Duration.Duration;
}

declare const isMoreThan: <A>(D: Durable<A>) => {
	(minimum: Duration.Duration): (self: A) => boolean;
	(self: A, minimum: Duration.Duration): boolean;
};

declare interface Appointment {
	duration: Duration.Duration;
}

declare const Appointment: {
	Durable: Durable<Appointment>;
};

declare const appointment: Appointment;
declare const appointments: Appointment[];

// Data-first: Direct function call
const hasMinimum = isMoreThan(Appointment.Durable)(
	appointment,
	Duration.hours(1)
);

// Data-last: Pipe-friendly
const hasMinimum2 = pipe(
	appointment,
	isMoreThan(Appointment.Durable)(Duration.hours(1))
);

// Partial application for filtering
const longAppointments = appointments.filter(
	isMoreThan(Appointment.Durable)(Duration.hours(1))
);
```

## Complete Typeclass Example

```typescript
import { Duration, Order } from 'effect';
import * as Function from 'effect/Function';

/**
 * Typeclass for types that have a duration.
 */
export interface Durable<A> {
	readonly getDuration: (self: A) => Duration.Duration;
	readonly setDuration: (self: A, duration: Duration.Duration) => A;
}

/**
 * Create a Durable instance.
 */
export const make = <A>(
	getDuration: (self: A) => Duration.Duration,
	setDuration: (self: A, duration: Duration.Duration) => A
): Durable<A> => ({
	getDuration,
	setDuration
});

/**
 * Check if duration is more than minimum.
 */
export const isMoreThan = <A>(D: Durable<A>) =>
	Function.dual<
		(minimum: Duration.Duration) => (self: A) => boolean,
		(self: A, minimum: Duration.Duration) => boolean
	>(2, (self: A, minimum: Duration.Duration): boolean =>
		Duration.isGreaterThanOrEqualTo(D.getDuration(self), minimum)
	);

/**
 * Order by duration.
 */
export const OrderByDuration = <A>(D: Durable<A>): Order.Order<A> =>
	Order.mapInput(Duration.Order, (self: A) => D.getDuration(self));
```

## When to Use

- Creating reusable abstractions (Schedulable, Durable, Priceable)
- Implementing operations that work across multiple types
- Providing composable, pipe-friendly APIs
- Enabling partial application for filtering/mapping

## Key Principles

1. **Curry everything** - Enable partial application
2. **Dual APIs always** - Support both usage styles
3. **Typeclass first** - First parameter is always the typeclass instance
4. **Type lambda for HKT** - Use TypeLambda pattern when needed
