Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:28:37 +08:00
commit 34edc9fd67
9 changed files with 2176 additions and 0 deletions

View File

@@ -0,0 +1,558 @@
---
name: functype-developer
description: Assist contributors working on the functype library codebase. Use this skill when creating new data structures, implementing functional interfaces (Functor, Monad, Foldable), adding tests, debugging library internals, or understanding functype's Scala-inspired architecture patterns including the Base pattern, HKT system, and Companion utilities.
---
# Functype Library Developer
## Overview
Guide for contributing to the functype TypeScript library. This skill provides architectural patterns, development workflows, and tooling for implementing new functional data structures following functype's Scala-inspired design.
## When to Use This Skill
Trigger this skill when:
- Creating new data structures or types
- Implementing functional interfaces (Functor, Monad, Foldable, etc.)
- Adding or fixing tests
- Understanding library architecture
- Debugging functional type implementations
- Following the Base pattern and type class system
- Working with the Feature Matrix
## Development Workflow
### Prerequisites
- **Node.js**: ≥ 18.0.0
- **pnpm**: 10.12.1
- **TypeScript**: Strict mode enabled
### Essential Commands
```bash
# Install dependencies
pnpm install
# Development (build with watch)
pnpm dev
# Before committing (MUST PASS)
pnpm validate
# Run tests
pnpm test
# Run specific test file
pnpm vitest run test/specific.spec.ts
# Check types without building
pnpm compile
```
### Pre-Commit Checklist
**Always run before committing:**
```bash
pnpm validate
```
This runs:
1. **Format**: Prettier formatting
2. **Lint**: ESLint checks
3. **Test**: All Vitest tests
4. **Build**: Production build
## Core Architecture
### Scala-Inspired Constructor Pattern
All types follow this pattern:
```typescript
// Constructor function returns object with methods
const option = Option(value) // Constructor
option.map((x) => x + 1) // Instance methods
Option.none() // Companion methods
```
**NOT class-based:**
```typescript
// ❌ Don't do this
class Option<T> {
constructor(value: T) { ... }
}
// ✅ Do this
export function Option<T>(value: T | null | undefined): OptionType<T> {
return value == null ? none() : some(value)
}
```
### Base Pattern
Use the `Base` function to add common functionality to all types:
```typescript
import { Base } from "@/core/base"
export function Option<T>(value: T | null | undefined): OptionType<T> {
if (value == null) {
return Base("None", {
// methods here
map: <B>(f: (val: T) => B) => Option.none<B>(),
// ...
})
}
return Base("Some", {
// methods here
map: <B>(f: (val: T) => B) => Option(f(value)),
// ...
})
}
```
**Base provides:**
- `Typeable` interface (type metadata)
- Standard `toString()` method
- Consistent object structure
### Type System Foundation
**Base constraint:**
```typescript
import type { Type } from "@/functor"
// Use Type instead of any
function process<T extends Type>(value: T): void {
// ...
}
```
**Never use `any`**:
- Use `unknown` for uncertain types
- Use `Type` for generic constraints
- Use proper type definitions
### Functional Interfaces
Every container type should implement these when applicable:
**Core interfaces** (see `references/architecture.md` for details):
- `Functor` - map over values
- `Applicative` - apply wrapped functions
- `Monad` - flatMap for sequencing
- `Foldable` - extract via pattern matching
- `Traversable` - collection operations
- `Serializable` - JSON/YAML/binary serialization
**Reference the Feature Matrix:**
Check `docs/FUNCTYPE_FEATURE_MATRIX.md` to see which interfaces each type implements and what methods are required.
## Creating a New Data Structure
### Step-by-Step Guide
**1. Research existing patterns**
```bash
# Use the Explore agent to understand the codebase
# Look at Option, Either, or Try as reference implementations
```
**2. Create module structure**
```bash
mkdir -p src/mynewtype
touch src/mynewtype/index.ts
touch test/mynewtype.spec.ts
```
**3. Use the template script**
```bash
# Run the new-type-template script
./claude/skills/functype-developer/scripts/new-type-template.sh MyNewType
```
This generates:
- Basic type structure
- Required interface implementations
- Test file boilerplate
**4. Implement the type**
Follow the constructor pattern:
```typescript
// src/mynewtype/index.ts
import { Base } from "@/core/base"
import type { Functor } from "@/functor"
export type MyNewType<T> = Functor<T> & {
// Your methods here
getValue(): T
}
export function MyNew<T>(value: T): MyNewType<T> {
return Base("MyNewType", {
// Functor
map: <B>(f: (val: T) => B): MyNewType<B> => {
return MyNew(f(value))
},
// Your methods
getValue: () => value,
// Pipe for composition
pipe: () => ({
map: (f: (val: T) => any) => MyNew(value).map(f),
}),
})
}
// Companion methods
MyNew.empty = <T>() => MyNew<T>(null as any)
```
**5. Add exports**
Update `src/index.ts`:
```typescript
export { MyNew } from "./mynewtype"
export type { MyNewType } from "./mynewtype"
```
Update `package.json` exports:
```json
{
"exports": {
"./mynewtype": {
"import": "./dist/esm/mynewtype/index.js",
"require": "./dist/cjs/mynewtype/index.js",
"types": "./dist/types/mynewtype/index.d.ts"
}
}
}
```
**6. Write comprehensive tests**
```typescript
// test/mynewtype.spec.ts
import { describe, expect, it } from "vitest"
import { MyNew } from "@/mynewtype"
describe("MyNewType", () => {
describe("Construction", () => {
it("should create from value", () => {
const value = MyNew(5)
expect(value.getValue()).toBe(5)
})
})
describe("Functor", () => {
it("should map over values", () => {
const result = MyNew(5).map((x) => x * 2)
expect(result.getValue()).toBe(10)
})
})
// More tests...
})
```
**7. Update Feature Matrix**
Add your type to `docs/FUNCTYPE_FEATURE_MATRIX.md` showing which interfaces it implements.
**8. Validate**
```bash
pnpm validate
```
### Interface Implementation Checklist
When implementing interfaces, refer to this checklist:
**Functor:**
- [ ] `map<B>(f: (value: A) => B): Functor<B>`
**Applicative (extends Functor):**
- [ ] `ap<B>(ff: Applicative<(value: A) => B>): Applicative<B>`
**Monad (extends Applicative):**
- [ ] `flatMap<B>(f: (value: A) => Monad<B>): Monad<B>`
**Foldable:**
- [ ] `fold<B>(onEmpty: () => B, onValue: (value: A) => B): B`
- [ ] `foldLeft<B>(z: B): (op: (b: B, a: A) => B) => B`
- [ ] `foldRight<B>(z: B): (op: (a: A, b: B) => B) => B`
See `references/architecture.md` for complete interface definitions.
## Testing Patterns
### Test Structure
Use Vitest with describe/it pattern:
```typescript
describe("TypeName", () => {
describe("Feature Group", () => {
it("should do specific thing", () => {
// Arrange
const input = createInput()
// Act
const result = performOperation(input)
// Assert
expect(result).toBe(expected)
})
})
})
```
### Property-Based Testing
Use fast-check for property tests:
```typescript
import { fc, test } from "@fast-check/vitest"
test.prop([fc.integer()])("should always return positive", (n) => {
const result = Math.abs(n)
expect(result).toBeGreaterThanOrEqual(0)
})
```
### Edge Cases to Test
Always test:
- Empty/null/undefined inputs
- Type inference correctness
- Method chaining
- Error cases
- Immutability (original unchanged)
## Code Style Guidelines
### Imports
```typescript
// Type-only imports when possible
import type { Type } from "@/functor"
import { Option } from "@/option"
// Organized with simple-import-sort (automatic)
```
### Types
```typescript
// ✅ Use Type for constraints
function process<T extends Type>(value: T): void
// ✅ Prefer types over interfaces
export type MyType<T> = {
value: T
}
// ✅ Explicit type annotations
function transform<T>(input: T): MyType<T> {
return { value: input }
}
```
### Naming
```typescript
// PascalCase for types
type OptionType<T> = { ... }
// camelCase for functions/variables
const someValue = Option(5)
function mapOption<T>(opt: OptionType<T>) { ... }
// Constructor functions are PascalCase
Option(value)
Either(value)
```
### Functional Style
```typescript
// ✅ Immutability
const newList = list.append(item)
// ❌ Mutation
list.push(item)
// ✅ Pure functions
function double(x: number): number {
return x * 2
}
// ❌ Side effects
function double(x: number): number {
console.log(x) // side effect
return x * 2
}
```
### Pattern Matching
```typescript
// ✅ Use Cond for conditionals
import { Cond } from "@/cond"
Cond.start<string>()
.case(x > 10, "big")
.case(x > 5, "medium")
.otherwise("small")
// ✅ Use Match for switches
import { Match } from "@/match"
Match(status)
.case("success", () => handleSuccess())
.case("error", () => handleError())
.done()
// ❌ Avoid early returns
// Use Cond or Option instead
```
## Common Development Tasks
### Adding a Helper Method
```typescript
// Add to type definition
export type MyType<T> = {
// existing methods...
// New helper
isEmpty(): boolean
}
// Implement in constructor
export function MyType<T>(value: T): MyType<T> {
return Base("MyType", {
// existing methods...
isEmpty: () => value == null,
})
}
```
### Implementing Serialization
```typescript
import { createSerializable } from "@/core/serializable"
export function MyType<T>(value: T): MyType<T> {
return Base("MyType", {
// other methods...
serialize: () =>
createSerializable({
type: "MyType",
value: value,
}),
})
}
```
### Adding Do-Notation Support
```typescript
export const MyTypeCompanion = {
Do: <T>(gen: () => Generator<MyType<any>, T, any>): MyType<T> => {
// Implementation here
// See Option or Either for reference
},
}
export const MyType = Object.assign(MyTypeConstructor, MyTypeCompanion)
```
## Debugging Tips
### TypeScript Errors
**"Type instantiation is excessively deep"**
- Check for circular type references
- Simplify nested generic types
- Use type aliases to break up complex types
**"Property 'X' does not exist on type 'Y'"**
- Ensure all interfaces are properly implemented
- Check that types are correctly exported
- Verify Base pattern includes all required methods
### Test Failures
```bash
# Run specific test with verbose output
pnpm vitest run test/mytype.spec.ts --reporter=verbose
# Run tests in watch mode
pnpm test:watch
# Check test coverage
pnpm test:coverage
```
### Build Issues
```bash
# Clean and rebuild
pnpm clean && pnpm build
# Check TypeScript compilation only
pnpm compile
# Analyze bundle size
pnpm analyze:size
```
## Resources
### scripts/
- `new-type-template.sh` - Generate boilerplate for new types
- `validate.sh` - Run the full validation workflow
### references/
- `architecture.md` - Detailed architecture and patterns
- `adding-types.md` - Complete guide for adding new data structures
- `testing-patterns.md` - Testing strategies and examples
### External Links
- **GitHub**: https://github.com/jordanburke/functype
- **Docs**: https://jordanburke.github.io/functype/
- **Feature Matrix**: `docs/FUNCTYPE_FEATURE_MATRIX.md`

View File

@@ -0,0 +1,376 @@
# Adding New Data Structures
Step-by-step guide for adding new types to functype.
## Complete Workflow
### 1. Planning
Before coding, answer these questions:
- **What problem does this type solve?**
- **Which functional interfaces should it implement?** (Check Feature Matrix)
- **What are the key operations?**
- **Are there similar types to reference?** (Option, Either, List)
### 2. Create Module Structure
```bash
# Create directories
mkdir -p src/mynewtype
mkdir -p test
# Create files
touch src/mynewtype/index.ts
touch test/mynewtype.spec.ts
```
### 3. Define Type Interface
```typescript
// src/mynewtype/index.ts
import type { Functor } from "@/functor"
import type { Foldable } from "@/foldable"
export type MyNewType<T> = Functor<T> &
Foldable<T> & {
// Core operations
getValue(): T
isEmpty(): boolean
// Additional methods
filter(predicate: (value: T) => boolean): MyNewType<T>
}
```
### 4. Implement Constructor
```typescript
import { Base } from "@/core/base"
export function MyNew<T>(value: T): MyNewType<T> {
return Base("MyNewType", {
// Functor
map: <B>(f: (val: T) => B): MyNewType<B> => {
return MyNew(f(value))
},
// Foldable
fold: <B>(onEmpty: () => B, onValue: (val: T) => B): B => {
return value == null ? onEmpty() : onValue(value)
},
foldLeft:
<B>(z: B) =>
(op: (b: B, a: T) => B): B => {
return value == null ? z : op(z, value)
},
foldRight:
<B>(z: B) =>
(op: (a: T, b: B) => B): B => {
return value == null ? z : op(value, z)
},
// Custom methods
getValue: () => value,
isEmpty: () => value == null,
filter: (predicate: (val: T) => boolean) => {
return predicate(value) ? MyNew(value) : MyNew(null as any)
},
// Pipe for composition
pipe: () => ({
map: (f: (val: T) => any) => MyNew(value).map(f),
filter: (p: (val: T) => boolean) => MyNew(value).filter(p),
}),
})
}
```
### 5. Add Companion Methods
```typescript
// Static factory methods
MyNew.empty = <T>(): MyNewType<T> => MyNew<T>(null as any)
MyNew.from = <T>(value: T | null | undefined): MyNewType<T> => {
return MyNew(value ?? (null as any))
}
MyNew.of = <T>(value: T): MyNewType<T> => MyNew(value)
```
### 6. Add Exports
**Update `src/index.ts`:**
```typescript
export { MyNew } from "./mynewtype"
export type { MyNewType } from "./mynewtype"
```
**Update `package.json` exports:**
```json
{
"exports": {
"./mynewtype": {
"import": "./dist/esm/mynewtype/index.js",
"require": "./dist/cjs/mynewtype/index.js",
"types": "./dist/types/mynewtype/index.d.ts"
}
}
}
```
### 7. Write Tests
```typescript
// test/mynewtype.spec.ts
import { describe, expect, it } from "vitest"
import { MyNew } from "@/mynewtype"
describe("MyNewType", () => {
describe("Construction", () => {
it("should create from value", () => {
const value = MyNew(5)
expect(value.getValue()).toBe(5)
expect(value.isEmpty()).toBe(false)
})
it("should create empty", () => {
const empty = MyNew.empty<number>()
expect(empty.isEmpty()).toBe(true)
})
})
describe("Functor", () => {
it("should map over values", () => {
const result = MyNew(5).map((x) => x * 2)
expect(result.getValue()).toBe(10)
})
it("should satisfy identity law", () => {
const value = MyNew(5)
expect(value.map((x) => x)).toEqual(value)
})
it("should satisfy composition law", () => {
const value = MyNew(5)
const f = (x: number) => x * 2
const g = (x: number) => x + 1
expect(value.map(f).map(g)).toEqual(value.map((x) => g(f(x))))
})
})
describe("Foldable", () => {
it("should fold with value", () => {
const result = MyNew(5).fold(
() => "empty",
(x) => `value: ${x}`,
)
expect(result).toBe("value: 5")
})
it("should fold when empty", () => {
const result = MyNew.empty<number>().fold(
() => "empty",
(x) => `value: ${x}`,
)
expect(result).toBe("empty")
})
})
describe("Custom Methods", () => {
it("should filter values", () => {
const value = MyNew(5)
expect(value.filter((x) => x > 3).getValue()).toBe(5)
expect(value.filter((x) => x > 10).isEmpty()).toBe(true)
})
})
describe("Edge Cases", () => {
it("should handle null", () => {
const value = MyNew(null)
expect(value.isEmpty()).toBe(true)
})
it("should handle undefined", () => {
const value = MyNew(undefined)
expect(value.isEmpty()).toBe(true)
})
})
})
```
### 8. Update Documentation
**Add to Feature Matrix (`docs/FUNCTYPE_FEATURE_MATRIX.md`):**
```markdown
| **MyNewType<T>** | ✓ | ✓ | ✓ | ... |
```
**Add help file (optional):**
```json
// src/mynewtype/mynewtype.help.json
{
"name": "MyNewType",
"description": "Brief description of the type",
"examples": [
{
"title": "Basic Usage",
"code": "const value = MyNew(5)"
}
]
}
```
### 9. Validate
```bash
pnpm validate
```
This runs:
- Format check
- Lint check
- All tests
- Production build
## Interface Implementation Guide
### Implementing Monad
If your type should support `flatMap`:
```typescript
export type MyNewType<T> = Functor<T> &
Monad<T> & {
// ...
}
export function MyNew<T>(value: T): MyNewType<T> {
return Base("MyNewType", {
// ... other methods
flatMap: <B>(f: (val: T) => MyNewType<B>): MyNewType<B> => {
return value == null ? MyNew.empty<B>() : f(value)
},
})
}
```
### Implementing Serializable
```typescript
import { createSerializable } from "@/core/serializable"
export function MyNew<T>(value: T): MyNewType<T> {
return Base("MyNewType", {
// ... other methods
serialize: () =>
createSerializable({
type: "MyNewType",
value: value,
}),
})
}
```
### Implementing Traversable (for collections)
```typescript
export function MyNewCollection<T>(items: T[]): MyNewCollectionType<T> {
return Base("MyNewCollection", {
// ... other methods
size: items.length,
isEmpty: items.length === 0,
contains: (value: T) => items.includes(value),
reduce: <B>(f: (acc: B, val: T) => B, initial: B): B => {
return items.reduce(f, initial)
},
})
}
```
## Common Patterns
### Handling Multiple Cases
```typescript
export function MyNew<T>(value: T): MyNewType<T> {
// Empty case
if (value == null) {
return Base("Empty", {
map: <B>(_f: (val: T) => B) => MyNew.empty<B>(),
// ... empty implementations
})
}
// Value case
return Base("Value", {
map: <B>(f: (val: T) => B) => MyNew(f(value)),
// ... value implementations
})
}
```
### Adding Type Guards
```typescript
export type MyNewType<T> = {
// ... other methods
isEmpty(): boolean
hasValue(): boolean
}
export function MyNew<T>(value: T): MyNewType<T> {
return Base("MyNewType", {
// ... other methods
isEmpty: () => value == null,
hasValue: () => value != null,
})
}
```
### Companion Pattern with TypeScript
```typescript
import { Companion } from "@/core/companion"
function MyNewConstructor<T>(value: T): MyNewType<T> {
// ... implementation
}
const MyNewCompanion = {
empty: <T>() => MyNewConstructor<T>(null as any),
of: <T>(value: T) => MyNewConstructor(value),
from: <T>(source: any) => MyNewConstructor<T>(/* convert source */),
}
export const MyNew = Companion(MyNewConstructor, MyNewCompanion)
```
## Checklist
Before submitting:
- [ ] Type interface defined with all required methods
- [ ] Constructor implemented using Base pattern
- [ ] All functional interfaces implemented correctly
- [ ] Companion methods added (empty, from, of, etc.)
- [ ] Exports added to src/index.ts
- [ ] Package.json exports configured
- [ ] Comprehensive tests written
- [ ] Edge cases tested (null, undefined, empty)
- [ ] Functor/Monad laws tested
- [ ] Feature Matrix updated
- [ ] `pnpm validate` passes
- [ ] Documentation added (optional help file)

View File

@@ -0,0 +1,555 @@
# Functype Architecture
Complete guide to functype's architectural patterns and design principles.
## Design Philosophy
Functype follows Scala's functional programming model adapted for TypeScript:
1. **Constructor functions** instead of classes
2. **Immutable** data structures
3. **Type-safe** operations with strict TypeScript
4. **Composable** APIs via function chaining
5. **Consistent** patterns across all types
## Core Patterns
### Constructor Pattern
All types use constructor functions that return objects with methods:
```typescript
// Pattern structure
export function TypeName<T>(value: T): TypeNameType<T> {
return Base("TypeName", {
// Interface methods
map: <B>(f: (val: T) => B) => TypeName(f(value)),
flatMap: <B>(f: (val: T) => TypeNameType<B>) => f(value),
// Custom methods
getValue: () => value,
// Pipe for composition
pipe: () => ({
map: (f: (val: T) => any) => TypeName(value).map(f),
}),
})
}
// Companion methods
TypeName.empty = <T>() => TypeName<T>(/* empty value */)
TypeName.from = <T>(source: T[]) => TypeName<T>(/* construct */)
```
**Why not classes?**
- Enables better tree-shaking
- Simpler type inference
- Aligns with functional programming principles
- More flexible composition
### Base Pattern
The `Base` function from `core/base` adds common functionality:
```typescript
import { Base } from "@/core/base"
export function Option<T>(value: T | null | undefined): OptionType<T> {
if (value == null) {
return Base("None", {
map: <B>(_f: (val: T) => B) => Option.none<B>(),
// ... other methods
})
}
return Base("Some", {
map: <B>(f: (val: T) => B) => Option(f(value)),
// ... other methods
})
}
```
**Base provides:**
- `Typeable` interface with type metadata
- Standard `toString()` method
- Consistent object structure
- Type-safe access to internal state
**Implementation:**
```typescript
export function Base<T extends string, B extends Record<string, any>>(type: T, body: B): Typeable & B {
return {
...body,
getType: () => type,
toString: () => `${type}(${JSON.stringify(body)})`,
}
}
```
### Companion Pattern
Use the `Companion` utility to create function-objects:
```typescript
import { Companion } from "@/core/companion"
// Define constructor
function OptionConstructor<T>(value: T | null | undefined): OptionType<T> {
// ... implementation
}
// Define companion methods
const OptionCompanion = {
none: <T>() => OptionConstructor<T>(null),
some: <T>(value: T) => OptionConstructor(value),
from: <T>(value: T | null | undefined) => OptionConstructor(value),
}
// Combine constructor and companion
export const Option = Companion(OptionConstructor, OptionCompanion)
```
**Result:**
```typescript
// Constructor usage
const opt = Option(5)
// Companion method usage
const none = Option.none()
const some = Option.some(10)
```
## Type System
### Base Type Constraint
```typescript
import type { Type } from "@/functor"
// Use Type for generic constraints
function process<T extends Type>(value: T): void {
// T can be any type
}
```
**Never use `any`:**
```typescript
// ❌ Wrong
function process(value: any): void
// ✅ Correct
function process<T extends Type>(value: T): void
// ✅ Or for uncertain types
function process(value: unknown): void
```
### Higher-Kinded Types (HKT)
Functype implements HKT to enable generic programming:
```typescript
// HKT allows abstracting over type constructors
type Functor<F, A> = {
map<B>(f: (a: A) => B): Functor<F, B>
}
// This allows writing generic functions that work with any Functor
function double<F, A extends number>(fa: Functor<F, A>): Functor<F, A> {
return fa.map((a) => a * 2)
}
// Works with Option<number>, Either<E, number>, List<number>, etc.
```
### Branded Types
Use the `Brand` module for nominal typing:
```typescript
import { Brand } from "@/brand"
// Create a branded type
type UserId = Brand<string, "UserId">
// Constructor with validation
const UserId = Brand<string, "UserId">(
(value: string): Either<string, string> => (value.length > 0 ? Right(value) : Left("UserId cannot be empty")),
)
// Usage
const userId = UserId.from("user-123").fold(
(error) => console.error(error),
(id) => processUser(id), // id has type UserId, not string
)
```
## Functional Interfaces
### Functor
Maps over contained values while preserving structure:
```typescript
export interface Functor<T> {
map<B>(f: (value: T) => B): Functor<B>
}
// Example implementation
export function Option<T>(value: T | null | undefined): OptionType<T> {
if (value == null) {
return Base("None", {
map: <B>(_f: (val: T) => B) => Option.none<B>(),
})
}
return Base("Some", {
map: <B>(f: (val: T) => B) => Option(f(value)),
})
}
```
**Laws:**
1. Identity: `fa.map(x => x) === fa`
2. Composition: `fa.map(f).map(g) === fa.map(x => g(f(x)))`
### Applicative
Applies wrapped functions to wrapped values:
```typescript
export interface Applicative<T> extends Functor<T> {
ap<B>(ff: Applicative<(value: T) => B>): Applicative<B>
}
// Example implementation
export function Option<T>(value: T | null | undefined): OptionType<T> {
return Base("Option", {
map: /* ... */,
ap: <B>(ff: OptionType<(value: T) => B>): OptionType<B> => {
return ff.flatMap(f => Option(value).map(f))
},
})
}
```
**Laws:**
1. Identity: `v.ap(pure(x => x)) === v`
2. Homomorphism: `pure(x).ap(pure(f)) === pure(f(x))`
3. Interchange: `u.ap(pure(y)) === pure(f => f(y)).ap(u)`
### Monad
Sequences operations that return wrapped values:
```typescript
export interface Monad<T> extends Applicative<T> {
flatMap<B>(f: (value: T) => Monad<B>): Monad<B>
}
// Example implementation
export function Option<T>(value: T | null | undefined): OptionType<T> {
return Base("Option", {
map: /* ... */,
ap: /* ... */,
flatMap: <B>(f: (val: T) => OptionType<B>): OptionType<B> => {
return value == null ? Option.none<B>() : f(value)
},
})
}
```
**Laws:**
1. Left identity: `pure(a).flatMap(f) === f(a)`
2. Right identity: `m.flatMap(pure) === m`
3. Associativity: `m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))`
### Foldable
Extracts values via pattern matching:
```typescript
export interface Foldable<T> {
fold<B>(onEmpty: () => B, onValue: (value: T) => B): B
foldLeft<B>(z: B): (op: (b: B, a: T) => B) => B
foldRight<B>(z: B): (op: (a: T, b: B) => B) => B
}
// Example implementation
export function Option<T>(value: T | null | undefined): OptionType<T> {
return Base("Option", {
fold: <B>(onEmpty: () => B, onValue: (val: T) => B): B => {
return value == null ? onEmpty() : onValue(value)
},
foldLeft:
<B>(z: B) =>
(op: (b: B, a: T) => B): B => {
return value == null ? z : op(z, value)
},
foldRight:
<B>(z: B) =>
(op: (a: T, b: B) => B): B => {
return value == null ? z : op(value, z)
},
})
}
```
### Traversable
Collections with size, iteration, and reduction:
```typescript
export interface Traversable<T> {
size: number
isEmpty: boolean
contains(value: T): boolean
reduce<B>(f: (acc: B, value: T) => B, initial: B): B
reduceRight<B>(f: (value: T, acc: B) => B, initial: B): B
}
// Example implementation
export function List<T>(items: T[]): ListType<T> {
return Base("List", {
size: items.length,
isEmpty: items.length === 0,
contains: (value: T) => items.includes(value),
reduce: <B>(f: (acc: B, val: T) => B, initial: B): B => {
return items.reduce(f, initial)
},
reduceRight: <B>(f: (val: T, acc: B) => B, initial: B): B => {
return items.reduceRight((acc, val) => f(val, acc), initial)
},
})
}
```
### Serializable
JSON/YAML/binary serialization:
```typescript
export interface Serializable {
serialize(): SerializationMethods<T>
}
export interface SerializationMethods<T> {
toJSON(): string
toYAML(): string
toBinary(): Uint8Array
}
// Example implementation
import { createSerializable } from "@/core/serializable"
export function Option<T>(value: T | null | undefined): OptionType<T> {
return Base("Option", {
serialize: () =>
createSerializable({
type: "Option",
value: value,
}),
})
}
```
## Module Organization
### Directory Structure
```
src/
├── core/ # Base patterns and utilities
│ ├── base.ts
│ ├── companion.ts
│ └── serializable.ts
├── functor/ # Functor type class
├── monad/ # Monad type class
├── option/ # Option type
│ └── index.ts
├── either/ # Either type
│ └── index.ts
└── index.ts # Main exports
test/
├── option.spec.ts
├── either.spec.ts
└── ...
```
### Index Exports
Each module has an `index.ts` that re-exports its main type:
```typescript
// src/option/index.ts
export { Option } from "./option"
export type { OptionType } from "./option"
```
Main index re-exports everything:
```typescript
// src/index.ts
export { Option } from "./option"
export type { OptionType } from "./option"
export { Either, Left, Right } from "./either"
export type { EitherType } from "./either"
// ...
```
### Package Exports
`package.json` supports selective imports:
```json
{
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts"
},
"./option": {
"import": "./dist/esm/option/index.js",
"require": "./dist/cjs/option/index.js",
"types": "./dist/types/option/index.d.ts"
}
}
}
```
**Usage:**
```typescript
// Import from main bundle
import { Option, Either } from "functype"
```
## Performance Considerations
### Tree-Shaking
Functype is optimized for tree-shaking:
```json
{
"sideEffects": false
}
```
This means unused exports are eliminated during bundling.
### Lazy Evaluation
Use `Lazy` and `LazyList` for deferred computation:
```typescript
import { Lazy } from "functype"
const expensive = Lazy(() => heavyComputation())
// Not computed yet
const value = expensive.value() // Computed once
const value2 = expensive.value() // Cached
```
### Immutability Cost
Immutable operations have overhead. For performance-critical code:
```typescript
// ❌ Slow for large lists
let result = List([])
for (let i = 0; i < 10000; i++) {
result = result.append(i) // Creates new list each time
}
// ✅ Fast with native array, then convert
const items = []
for (let i = 0; i < 10000; i++) {
items.push(i)
}
const result = List(items) // Single conversion
```
## Error Handling
### Error Patterns
```typescript
// ✅ Use Option for nullable values
Option(value).orElse(default)
// ✅ Use Either for errors with context
validateEmail(email)
.fold(
error => console.error(error),
validEmail => send(validEmail)
)
// ✅ Use Try for exceptions
Try(() => JSON.parse(str))
.recover(error => defaultValue)
// ✅ Use Throwable for unexpected errors
throw new Throwable("Unexpected error", { context })
```
### ErrorFormatter
Use for structured error output:
```typescript
import { ErrorFormatter } from "@/core/error"
const error = new Error("Something went wrong")
const formatted = ErrorFormatter.format(error)
console.error(formatted.toString())
// Error: Something went wrong
// at myFunction (file.ts:10:5)
// ...
```
## Testing Architecture
### Test Organization
```
test/
├── option.spec.ts # Option tests
├── either.spec.ts # Either tests
├── integration/ # Integration tests
│ └── composition.spec.ts
└── property/ # Property-based tests
└── functor.spec.ts
```
### Test Patterns
```typescript
describe("Option", () => {
describe("Construction", () => {
it("should create Some from non-null value", () => {
const opt = Option(5)
expect(opt.isSome()).toBe(true)
})
})
describe("Functor Laws", () => {
it("should satisfy identity", () => {
const opt = Option(5)
expect(opt.map((x) => x)).toEqual(opt)
})
})
})
```

View File

@@ -0,0 +1,394 @@
# Testing Patterns
Comprehensive testing strategies for functype development.
## Test Structure
### Standard Pattern
```typescript
import { describe, expect, it } from "vitest"
import { MyType } from "@/mytype"
describe("MyType", () => {
describe("Construction", () => {
it("should create from value", () => {
// Arrange
const value = 5
// Act
const result = MyType(value)
// Assert
expect(result.getValue()).toBe(5)
})
})
describe("Interface Implementation", () => {
// Group tests by interface
})
describe("Custom Methods", () => {
// Test type-specific methods
})
describe("Edge Cases", () => {
// Test null, undefined, empty
})
})
```
## Testing Functional Interfaces
### Functor Laws
```typescript
describe("Functor Laws", () => {
it("should satisfy identity law", () => {
const fa = MyType(5)
const identity = (x: any) => x
expect(fa.map(identity)).toEqual(fa)
})
it("should satisfy composition law", () => {
const fa = MyType(5)
const f = (x: number) => x * 2
const g = (x: number) => x + 1
const left = fa.map(f).map(g)
const right = fa.map((x) => g(f(x)))
expect(left).toEqual(right)
})
})
```
### Monad Laws
```typescript
describe("Monad Laws", () => {
it("should satisfy left identity", () => {
const a = 5
const f = (x: number) => MyType(x * 2)
expect(MyType(a).flatMap(f)).toEqual(f(a))
})
it("should satisfy right identity", () => {
const m = MyType(5)
expect(m.flatMap((x) => MyType(x))).toEqual(m)
})
it("should satisfy associativity", () => {
const m = MyType(5)
const f = (x: number) => MyType(x * 2)
const g = (x: number) => MyType(x + 1)
const left = m.flatMap(f).flatMap(g)
const right = m.flatMap((x) => f(x).flatMap(g))
expect(left).toEqual(right)
})
})
```
### Applicative Laws
```typescript
describe("Applicative Laws", () => {
it("should satisfy identity", () => {
const v = MyType(5)
const pure = MyType((x: number) => x)
expect(v.ap(pure)).toEqual(v)
})
it("should satisfy homomorphism", () => {
const f = (x: number) => x * 2
const x = 5
const left = MyType(x).ap(MyType(f))
const right = MyType(f(x))
expect(left).toEqual(right)
})
})
```
## Property-Based Testing
### Using fast-check
```typescript
import { fc, test } from "@fast-check/vitest"
test.prop([fc.integer()])("map preserves structure", (n) => {
const opt = Option(n)
const result = opt.map((x) => x * 2)
if (opt.isSome()) {
expect(result.isSome()).toBe(true)
} else {
expect(result.isNone()).toBe(true)
}
})
test.prop([fc.integer(), fc.integer()])("flatMap is associative", (a, b) => {
const m = List([a, b])
const f = (x: number) => List([x, x * 2])
const g = (x: number) => List([x + 1])
const left = m.flatMap(f).flatMap(g)
const right = m.flatMap((x) => f(x).flatMap(g))
expect(left.toArray()).toEqual(right.toArray())
})
```
### Custom Arbitraries
```typescript
import { fc } from "@fast-check/vitest"
// Generate Options
const optionArb = <T>(valueArb: fc.Arbitrary<T>): fc.Arbitrary<Option<T>> =>
fc.oneof(
fc.constant(Option.none()),
valueArb.map((v) => Option(v)),
)
// Generate Lists
const listArb = <T>(valueArb: fc.Arbitrary<T>): fc.Arbitrary<List<T>> => fc.array(valueArb).map((arr) => List(arr))
// Usage
test.prop([optionArb(fc.integer())])("option property", (opt) => {
// Test with generated Options
})
```
## Edge Case Testing
### Null/Undefined
```typescript
describe("Edge Cases", () => {
it("should handle null", () => {
const value = Option(null)
expect(value.isNone()).toBe(true)
})
it("should handle undefined", () => {
const value = Option(undefined)
expect(value.isNone()).toBe(true)
})
it("should handle zero", () => {
const value = Option(0)
expect(value.isSome()).toBe(true)
})
it("should handle empty string", () => {
const value = Option("")
expect(value.isSome()).toBe(true)
})
})
```
### Type Inference
```typescript
describe("Type Inference", () => {
it("should infer correct types", () => {
const num: Option<number> = Option(5)
const str: Option<string> = num.map((x) => x.toString())
// TypeScript should not error
const _check: string = str.orElse("")
})
})
```
### Immutability
```typescript
describe("Immutability", () => {
it("should not mutate original", () => {
const original = List([1, 2, 3])
const modified = original.append(4)
expect(original.toArray()).toEqual([1, 2, 3])
expect(modified.toArray()).toEqual([1, 2, 3, 4])
})
})
```
## Integration Testing
### Type Composition
```typescript
describe("Type Composition", () => {
it("should compose Option and Either", () => {
const validate = (x: number): Either<string, number> => (x > 0 ? Right(x) : Left("Must be positive"))
const result = Option(5)
.map(validate)
.fold(
() => Left("No value"),
(either) => either,
)
expect(result.isRight()).toBe(true)
})
})
```
### Pipeline Testing
```typescript
describe("Pipelines", () => {
it("should compose operations", () => {
const result = List([1, 2, 3, 4, 5])
.filter((x) => x > 2)
.map((x) => x * 2)
.foldLeft(0)((sum, x) => sum + x)
expect(result).toBe(24) // (3 + 4 + 5) * 2 = 24
})
})
```
## Performance Testing
### Benchmarks
```typescript
import { bench, describe } from "vitest"
describe("Performance", () => {
bench("Option creation", () => {
for (let i = 0; i < 1000; i++) {
Option(i)
}
})
bench("List operations", () => {
const list = List(Array.from({ length: 1000 }, (_, i) => i))
list.filter((x) => x % 2 === 0).map((x) => x * 2)
})
})
```
## Test Organization
### File Structure
```
test/
├── option.spec.ts # Option tests
├── either.spec.ts # Either tests
├── list.spec.ts # List tests
├── integration/ # Integration tests
│ ├── composition.spec.ts
│ └── pipelines.spec.ts
└── property/ # Property-based tests
├── functor-laws.spec.ts
└── monad-laws.spec.ts
```
### Naming Conventions
```typescript
// ✅ Good test names
it("should create Some from non-null value")
it("should return None when mapping over None")
it("should satisfy functor identity law")
// ❌ Poor test names
it("works")
it("test1")
it("should work correctly")
```
## Coverage
### Running Coverage
```bash
pnpm test:coverage
```
### Coverage Goals
- **Line coverage**: >90%
- **Branch coverage**: >85%
- **Function coverage**: >90%
### Uncovered Code
Some code is intentionally not covered:
- Error handling for impossible states
- Debug utilities
- Deprecated code
Mark with comments:
```typescript
/* istanbul ignore next */
if (impossibleCondition) {
throw new Error("This should never happen")
}
```
## Continuous Testing
### Watch Mode
```bash
pnpm test:watch
```
### Pre-commit Hook
```bash
# .git/hooks/pre-commit
#!/bin/sh
pnpm test || exit 1
```
## Debugging Tests
### Verbose Output
```bash
pnpm vitest run test/mytype.spec.ts --reporter=verbose
```
### Focused Tests
```typescript
// Run only this test
it.only("should do something", () => {
// ...
})
// Skip this test
it.skip("should do something else", () => {
// ...
})
```
### Test Timeout
```typescript
it(
"should handle slow operation",
async () => {
// ...
},
{ timeout: 10000 },
) // 10 second timeout
```

View File

@@ -0,0 +1,153 @@
#!/bin/bash
# Script to generate boilerplate for a new functype data structure
# Usage: ./new-type-template.sh TypeName
set -e
if [ $# -eq 0 ]; then
echo "Usage: $0 TypeName"
echo "Example: $0 Result"
exit 1
fi
TYPE_NAME=$1
TYPE_NAME_LOWER=$(echo "$TYPE_NAME" | tr '[:upper:]' '[:lower:]')
PROJECT_ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
echo "Creating new type: $TYPE_NAME"
echo "Project root: $PROJECT_ROOT"
# Create source directory
SRC_DIR="$PROJECT_ROOT/src/$TYPE_NAME_LOWER"
mkdir -p "$SRC_DIR"
# Create type implementation
cat > "$SRC_DIR/index.ts" << EOF
import { Base } from "@/core/base"
import type { Functor } from "@/functor"
import type { Foldable } from "@/foldable"
/**
* $TYPE_NAME type - [TODO: Add description]
*/
export type ${TYPE_NAME}Type<T> = Functor<T> & Foldable<T> & {
// Core methods
getValue(): T
isEmpty(): boolean
// TODO: Add additional methods
}
/**
* Create a $TYPE_NAME from a value
*/
export function $TYPE_NAME<T>(value: T): ${TYPE_NAME}Type<T> {
return Base("$TYPE_NAME", {
// Functor
map: <B>(f: (val: T) => B): ${TYPE_NAME}Type<B> => {
return $TYPE_NAME(f(value))
},
// Foldable
fold: <B>(onEmpty: () => B, onValue: (val: T) => B): B => {
return value == null ? onEmpty() : onValue(value)
},
foldLeft: <B>(z: B) => (op: (b: B, a: T) => B): B => {
return value == null ? z : op(z, value)
},
foldRight: <B>(z: B) => (op: (a: T, b: B) => B): B => {
return value == null ? z : op(value, z)
},
// Core methods
getValue: () => value,
isEmpty: () => value == null,
// Pipe
pipe: () => ({
map: (f: (val: T) => any) => $TYPE_NAME(value).map(f),
}),
})
}
// Companion methods
$TYPE_NAME.empty = <T>(): ${TYPE_NAME}Type<T> => $TYPE_NAME<T>(null as any)
$TYPE_NAME.of = <T>(value: T): ${TYPE_NAME}Type<T> => $TYPE_NAME(value)
EOF
# Create test file
TEST_FILE="$PROJECT_ROOT/test/$TYPE_NAME_LOWER.spec.ts"
cat > "$TEST_FILE" << EOF
import { describe, expect, it } from "vitest"
import { $TYPE_NAME } from "@/$TYPE_NAME_LOWER"
describe("$TYPE_NAME", () => {
describe("Construction", () => {
it("should create from value", () => {
const value = $TYPE_NAME(5)
expect(value.getValue()).toBe(5)
expect(value.isEmpty()).toBe(false)
})
it("should create empty", () => {
const empty = $TYPE_NAME.empty<number>()
expect(empty.isEmpty()).toBe(true)
})
})
describe("Functor", () => {
it("should map over values", () => {
const result = $TYPE_NAME(5).map(x => x * 2)
expect(result.getValue()).toBe(10)
})
it("should satisfy identity law", () => {
const value = $TYPE_NAME(5)
expect(value.map(x => x)).toEqual(value)
})
it("should satisfy composition law", () => {
const value = $TYPE_NAME(5)
const f = (x: number) => x * 2
const g = (x: number) => x + 1
expect(value.map(f).map(g)).toEqual(value.map(x => g(f(x))))
})
})
describe("Foldable", () => {
it("should fold with value", () => {
const result = $TYPE_NAME(5).fold(
() => "empty",
x => \`value: \${x}\`
)
expect(result).toBe("value: 5")
})
})
describe("Edge Cases", () => {
it("should handle null", () => {
const value = $TYPE_NAME(null)
expect(value.isEmpty()).toBe(true)
})
})
})
EOF
echo ""
echo "✅ Created files:"
echo " - $SRC_DIR/index.ts"
echo " - $TEST_FILE"
echo ""
echo "Next steps:"
echo "1. Update src/index.ts to export $TYPE_NAME"
echo "2. Update package.json exports to include ./$TYPE_NAME_LOWER"
echo "3. Implement additional methods in $SRC_DIR/index.ts"
echo "4. Add more tests in $TEST_FILE"
echo "5. Update docs/FUNCTYPE_FEATURE_MATRIX.md"
echo "6. Run: pnpm validate"
EOF

View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Run the full functype validation workflow
# This script should be run before committing any changes
set -e
PROJECT_ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
cd "$PROJECT_ROOT"
echo "======================================"
echo " Functype Validation Workflow"
echo "======================================"
echo ""
# Step 1: Format
echo "📝 Step 1/4: Formatting code with Prettier..."
pnpm format || {
echo "❌ Formatting failed!"
echo "Run 'pnpm format' to fix formatting issues"
exit 1
}
echo "✅ Formatting complete"
echo ""
# Step 2: Lint
echo "🔍 Step 2/4: Linting code with ESLint..."
pnpm lint || {
echo "❌ Linting failed!"
echo "Run 'pnpm lint' to fix linting issues"
exit 1
}
echo "✅ Linting complete"
echo ""
# Step 3: Test
echo "🧪 Step 3/4: Running tests with Vitest..."
pnpm test || {
echo "❌ Tests failed!"
echo "Fix failing tests and try again"
exit 1
}
echo "✅ Tests complete"
echo ""
# Step 4: Build
echo "🔨 Step 4/4: Building project..."
pnpm build || {
echo "❌ Build failed!"
echo "Fix build errors and try again"
exit 1
}
echo "✅ Build complete"
echo ""
echo "======================================"
echo " ✅ All validation checks passed!"
echo "======================================"
echo ""
echo "Your code is ready to commit."
EOF