typeclass-design

Implement typeclasses with curried signatures and dual APIs for both data-first and data-last usage

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "typeclass-design" with this command: npx skills add front-depiction/claude-setup/front-depiction-claude-setup-typeclass-design

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:

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.DurationInput) =>
  (self: A): boolean => {
    const current = D.getDuration(self)
    return Duration.greaterThanOrEqualTo(current, minimum)
  }

Pattern: Dual APIs

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

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.DurationInput) => (self: A) => boolean,
    // Data-first (uncurried) - direct call
    (self: A, minimum: Duration.DurationInput) => boolean
  >(
    2,  // Number of arguments for data-first form
    (self: A, minimum: Duration.DurationInput): boolean => {
      const current = D.getDuration(self)
      return Duration.greaterThanOrEqualTo(current, minimum)
    }
  )

Usage Patterns

The dual API enables both styles:

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.DurationInput): (self: A) => boolean
  (self: A, minimum: Duration.DurationInput): 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

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.DurationInput) => A
}

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

/**
 * Check if duration is more than minimum.
 */
export const isMoreThan = <A>(D: Durable<A>) =>
  Function.dual<
    (minimum: Duration.DurationInput) => (self: A) => boolean,
    (self: A, minimum: Duration.DurationInput) => boolean
  >(
    2,
    (self: A, minimum: Duration.DurationInput): boolean =>
      Duration.greaterThanOrEqualTo(
        D.getDuration(self),
        Duration.decode(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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

command-executor

No summary provided by upstream source.

Repository SourceNeeds Review
General

react-composition

No summary provided by upstream source.

Repository SourceNeeds Review
General

effect-ai-streaming

No summary provided by upstream source.

Repository SourceNeeds Review
General

wide-events

No summary provided by upstream source.

Repository SourceNeeds Review