receta
Compare

Type-Specific Comparators

Type-specific comparators provide specialized sorting for dates, numbers, strings, and booleans with proper handling of edge cases.

Overview

FunctionTypeHandlesExample
byDate(fn)DateChronological orderingEvent timelines, logs
byNumber(fn)numberNaN, InfinityPrices, scores, metrics
byString(fn, options)stringCase, localeNames, titles
byBoolean(fn)booleanfalse < trueFlags, toggles

byDate()

Sorts Date objects chronologically (earliest to latest).

Signature

Signature
function byDate<T>(fn: (value: T) => Date): Comparator<T>

Why Use byDate() vs ascending()?

// ✅ byDate - explicitly handles dates
events.sort(byDate(e => e.createdAt))

// ⚠️  ascending - works but less clear
events.sort(ascending(e => e.createdAt))

// Both work, but byDate() signals intent and may optimize internally

Examples

Event Timeline

import { byDate } from '@ontologyio/receta/compare'

interface Event {
  name: string
  occurredAt: Date
}

const events: Event[] = [
  { name: 'Launch', occurredAt: new Date('2024-03-01') },
  { name: 'Meeting', occurredAt: new Date('2024-02-15') },
  { name: 'Review', occurredAt: new Date('2024-02-20') }
]

// Chronological (oldest first)
events.sort(byDate(e => e.occurredAt))
// => [Meeting (Feb 15), Review (Feb 20), Launch (Mar 1)]

Most Recent First

import { reverse, byDate } from '@ontologyio/receta/compare'

// Newest first
events.sort(reverse(byDate(e => e.occurredAt)))
// => [Launch (Mar 1), Review (Feb 20), Meeting (Feb 15)]

Real-World: GitHub Commits

interface Commit {
  sha: string
  message: string
  authoredAt: Date
  committedAt: Date
}

// Sort by commit date (most recent first)
commits.sort(reverse(byDate(c => c.committedAt)))

// Sort by author date
commits.sort(byDate(c => c.authoredAt))

Real-World: Stripe Transactions

interface Transaction {
  id: string
  amount: number
  created: Date
}

// Most recent transactions first
transactions.sort(reverse(byDate(t => t.created)))

Log Files

interface LogEntry {
  level: string
  message: string
  timestamp: Date
}

// Chronological log order
logs.sort(byDate(l => l.timestamp))

byNumber()

Sorts numbers with proper handling of NaN and Infinity.

Signature

Signature
function byNumber<T>(fn: (value: T) => number): Comparator<T>

Special Value Handling

  • NaN: Treated as equal to each other, sorted to the end
  • Infinity: Sorted as the largest value
  • -Infinity: Sorted as the smallest value

Examples

Basic Number Sorting

import { byNumber } from '@ontologyio/receta/compare'

interface Metric {
  name: string
  value: number
}

const metrics: Metric[] = [
  { name: 'CPU', value: 45.2 },
  { name: 'Memory', value: 78.9 },
  { name: 'Disk', value: 23.1 }
]

// Lowest to highest
metrics.sort(byNumber(m => m.value))
// => [Disk (23.1), CPU (45.2), Memory (78.9)]

Handling NaN

const values = [
  { name: 'A', score: 100 },
  { name: 'B', score: NaN },
  { name: 'C', score: 50 },
  { name: 'D', score: NaN }
]

values.sort(byNumber(v => v.score))
// => [C (50), A (100), B (NaN), D (NaN)]
//   NaN values grouped at end

Handling Infinity

const limits = [
  { name: 'Basic', max: 100 },
  { name: 'Unlimited', max: Infinity },
  { name: 'Trial', max: 10 }
]

limits.sort(byNumber(l => l.max))
// => [Trial (10), Basic (100), Unlimited (Infinity)]

Real-World: Analytics Dashboard

interface KPI {
  metric: string
  current: number
  change: number
}

// Sort by largest change (positive or negative)
kpis.sort(descending(byNumber(k => Math.abs(k.change))))

Real-World: Pricing Tiers

interface PricingTier {
  name: string
  pricePerMonth: number
  userLimit: number
}

// Cheapest plans first
tiers.sort(byNumber(t => t.pricePerMonth))

// Plans with most users first
tiers.sort(reverse(byNumber(t => t.userLimit)))

byString()

Sorts strings with options for case sensitivity and locale awareness.

Signature

function byString<T>(
  fn: (value: T) => string,
  options?: StringCompareOptions
): Comparator<T>

interface StringCompareOptions {
  caseSensitive?: boolean  // default: true
  locale?: string          // default: undefined
}

Examples

Case-Sensitive (Default)

import { byString } from '@ontologyio/receta/compare'

const users = [
  { name: 'alice' },
  { name: 'Bob' },
  { name: 'Charlie' }
]

// Default: case-sensitive (uppercase before lowercase)
users.sort(byString(u => u.name))
// => [Bob, Charlie, alice]
//     ^uppercase first

Case-Insensitive

// Ignore case
users.sort(byString(u => u.name, { caseSensitive: false }))
// => [alice, Bob, Charlie]
//     ^alphabetical regardless of case

Locale-Aware Sorting

const cities = [
  { name: 'Zürich' },
  { name: 'Berlin' },
  { name: 'München' }
]

// German locale (handles ü, ö correctly)
cities.sort(byString(c => c.name, { locale: 'de-DE' }))

Real-World: User Directory

interface User {
  firstName: string
  lastName: string
  email: string
}

// Sort by last name, case-insensitive
users.sort(byString(u => u.lastName, { caseSensitive: false }))

// Sort by email (case-sensitive for domains)
users.sort(byString(u => u.email))

Real-World: Internationalized Product Catalog

interface Product {
  name: string
  category: string
}

// French locale for product names
const frenchProducts = products.filter(p => p.locale === 'fr')
frenchProducts.sort(byString(p => p.name, { locale: 'fr-FR' }))

byBoolean()

Sorts boolean values (false < true).

Signature

Signature
function byBoolean<T>(fn: (value: T) => boolean): Comparator<T>

Default Order

  • false comes before true (false < true)
  • Use reverse() for true-first sorting

Examples

Incomplete Tasks First

import { byBoolean } from '@ontologyio/receta/compare'

interface Task {
  title: string
  completed: boolean
}

const tasks: Task[] = [
  { title: 'Review PR', completed: true },
  { title: 'Fix bug', completed: false },
  { title: 'Write docs', completed: false }
]

// Incomplete (false) first
tasks.sort(byBoolean(t => t.completed))
// => [Fix bug (false), Write docs (false), Review PR (true)]

Urgent Items First

import { reverse, byBoolean } from '@ontologyio/receta/compare'

interface Item {
  name: string
  urgent: boolean
}

// Urgent (true) first
items.sort(reverse(byBoolean(i => i.urgent)))
// => [urgent items... non-urgent items...]

Multi-Level: Urgent + Incomplete

import { compose, reverse, byBoolean } from '@ontologyio/receta/compare'

interface Task {
  title: string
  urgent: boolean
  completed: boolean
}

// Urgent first, then incomplete first within each urgency level
tasks.sort(
  compose(
    reverse(byBoolean(t => t.urgent)),
    byBoolean(t => t.completed)
  )
)
// => [Urgent incomplete, Urgent complete, Normal incomplete, Normal complete]

Real-World: Feature Flags

interface FeatureFlag {
  name: string
  enabled: boolean
  rolloutPercentage: number
}

// Enabled features first, then by rollout percentage
flags.sort(
  compose(
    reverse(byBoolean(f => f.enabled)),
    descending(f => f.rolloutPercentage)
  )
)

Real-World: Email Inbox

interface Email {
  subject: string
  isStarred: boolean
  isRead: boolean
  receivedAt: Date
}

// Starred first, then unread, then by date
emails.sort(
  compose(
    reverse(byBoolean(e => e.isStarred)),
    byBoolean(e => e.isRead),
    reverse(byDate(e => e.receivedAt))
  )
)

Comparison Table

TypeFunctionDefault OrderReverse For
DatebyDate(fn)Oldest → NewestMost recent first
NumberbyNumber(fn)Low → HighHighest first
StringbyString(fn)A → Z (case-sensitive)Z → A, case-insensitive
BooleanbyBoolean(fn)false → truetrue first (active/enabled)

When to Use Each

Use byDate() when:

  • Sorting events, logs, or transactions
  • Chronological ordering matters
  • Need "most recent first" or "oldest first"

Use byNumber() when:

  • Sorting prices, scores, metrics, ratings
  • Data may contain NaN or Infinity
  • Numeric comparison (not lexicographic)

Use byString() when:

  • Sorting names, titles, categories
  • Need case-insensitive sorting
  • Working with internationalized content
  • Default ascending() doesn't handle case/locale correctly

Use byBoolean() when:

  • Sorting by flags, toggles, status (active/inactive)
  • Grouping by binary states
  • Need explicit true/false ordering

Common Patterns

Pattern: Sort by Date Range

interface Event {
  startDate: Date
  endDate: Date
}

// Sort by start date, end date breaks ties
events.sort(
  compose(
    byDate(e => e.startDate),
    byDate(e => e.endDate)
  )
)

Pattern: Sort by Computed Number

interface Product {
  price: number
  quantity: number
}

// Sort by total value (price * quantity)
products.sort(byNumber(p => p.price * p.quantity))

Pattern: Multi-Language Support

function sortByName(users: User[], locale: string) {
  return users.sort(byString(u => u.name, { locale }))
}

sortByName(users, 'en-US')  // English
sortByName(users, 'de-DE')  // German
sortByName(users, 'ja-JP')  // Japanese

Pattern: Status Flags

interface Account {
  isVerified: boolean
  isPremium: boolean
  isActive: boolean
}

// Premium verified active accounts first
accounts.sort(
  compose(
    reverse(byBoolean(a => a.isPremium)),
    reverse(byBoolean(a => a.isVerified)),
    reverse(byBoolean(a => a.isActive))
  )
)

Next Steps

On this page