import { pipe } from "../prelude/compose" import { none, type Option, some } from "../prelude/option" // Phantom property type declare const __props: unique symbol type Props

= { [__props]?: P } /** * Tracked array with phantom properties. * Properties like "Sorted" or "NonEmpty" are tracked at type level. */ export type Arr = readonly T[] & Props

// Property markers export type Sorted = "Sorted" export type NonEmpty = "NonEmpty" // Constructor export const arr = (xs: readonly T[]): Arr => xs as Arr // NonEmpty check - returns Option to track the property export const nonEmpty = ( xs: Arr, ): Option> => xs.length > 0 ? some(xs as Arr) : none // Safe accessors for NonEmpty arrays export const head = (xs: Arr): T => xs[0]! export const last = (xs: Arr): T => xs[xs.length - 1]! // Sorting - adds Sorted property export const sortNum =

( xs: Arr, ): Arr => [...xs].sort((a, b) => a - b) as Arr export const sortBy = (key: (t: T) => number) => (xs: Arr): Arr => [...xs].sort((a, b) => key(a) - key(b)) as Arr // Transformations - preserve properties where valid export const map = (f: (t: T) => U) =>

(xs: Arr): Arr> => xs.map(f) as Arr> export const filter = (pred: (t: T) => boolean) =>

(xs: Arr): Arr> => xs.filter(pred) as Arr> export const reduce = (f: (acc: U, t: T) => U, initial: U) =>

(xs: Arr): U => xs.reduce(f, initial) // Take/drop export const take = (n: number) => (xs: Arr): Arr> => xs.slice(0, n) as Arr> export const drop = (n: number) => (xs: Arr): Arr> => xs.slice(n) as Arr> // Generic binary search with comparator export const binarySearch = (compare: (a: T, b: T) => number) => (target: T) =>

(xs: Arr): Option => { const go = (lo: number, hi: number): Option => lo > hi ? none : pipe(Math.floor((lo + hi) / 2), (mid) => ((cmp) => cmp === 0 ? some(mid) : cmp < 0 ? go(lo, mid - 1) : go(mid + 1, hi))(compare(target, xs[mid]!)), ) return go(0, xs.length - 1) } // Binary search specialized for numbers export const binarySearchNum = binarySearch((a, b) => a - b)