Back to skills

Builtin Developer

Guides development of AILANG builtin functions. Use when user wants to add a builtin function, register new builtins, or understand the builtin system. Reduces development time from 7.5h to 2.5h (-67%).

11 stars
0 votes
0 copies
0 views
Added 12/19/2025
data-aigobashexpresstesting

Works with

cli

Install via CLI

$openskills install sunholo-data/ailang
Download Zip
Files
SKILL.md
---
name: Builtin Developer
description: Guides development of AILANG builtin functions. Use when user wants to add a builtin function, register new builtins, or understand the builtin system. Reduces development time from 7.5h to 2.5h (-67%).
---

# Builtin Developer

Add new builtin functions to AILANG using the modern registry system (M-DX1).

## Quick Start

**Development time: ~2.5 hours** (down from 7.5h with legacy system)

**Most common workflow:**
1. Register builtin with metadata (~30 min)
2. Write hermetic tests (~1 hour)
3. Validate and inspect (~30 min)
4. Runtime auto-wires (already done!)

## When to Use This Skill

Invoke this skill when:
- User wants to add a new builtin function
- User asks about builtin registration process
- User needs to understand the builtin system
- User wants to validate or inspect builtins
- User asks "how do I add a builtin?"

## System Overview

**Status**: 🎉 **M-DX1 COMPLETE (Oct 2025)** - 52 builtins migrated, fully documented

**Key Benefits:**
- **67% faster**: 2.5h instead of 7.5h
- **1 file instead of 4**: Single-point registration
- **71% less code**: Type Builder DSL reduces boilerplate
- **Automatic wiring**: Registry connects to runtime/link
- **Hermetic tests**: MockEffContext with HTTP/FS mocking

**Components:**
- **Central Registry** (`internal/builtins/spec.go`) - Single registration point
- **Type Builder DSL** (`internal/types/builder.go`) - Fluent type construction
- **Test Harness** (`internal/effects/testctx/`) - Hermetic testing helpers
- **CLI Commands** - Validation and inspection tools

## Available Scripts

### `scripts/validate_builtins.sh`
Validate all builtins in the registry.

**Usage:**
```bash
.claude/skills/builtin-developer/scripts/validate_builtins.sh
```

**What it checks:**
- All builtins are registered
- Proper metadata structure
- Type signatures valid
- Test coverage exists

### `scripts/check_builtin_health.sh`
Run ailang doctor and list commands.

**Usage:**
```bash
.claude/skills/builtin-developer/scripts/check_builtin_health.sh
```

## Workflow

### Step 1: Register the Builtin (~30 min)

**Create or edit module file** (e.g., `internal/builtins/string.go`):

```go
// internal/builtins/string.go
func init() {
    registerMyBuiltin()
}

func registerMyBuiltin() {
    RegisterEffectBuiltin(BuiltinSpec{
        Module:  "std/string",
        Name:    "_str_reverse",
        NumArgs: 1,
        IsPure:  true,        // or false with Effect: "IO"
        Type:    makeReverseType,
        Impl:    strReverseImpl,
        Metadata: &BuiltinMetadata{
            Description: "Reverse a string (Unicode-aware)",
            Params: []ParamDoc{
                {Name: "s", Description: "String to reverse"},
            },
            Returns: "Reversed string",
            Examples: []Example{
                {Code: `_str_reverse("hello")`, Description: "Returns \"olleh\""},
            },
            Since:     "v0.3.15",
            Stability: StabilityStable,
            Tags:      []string{"string", "reverse", "unicode"},
            Category:  "string",
        },
    })
}

func makeReverseType() types.Type {
    T := types.NewBuilder()
    return T.Func(T.String()).Returns(T.String())
}

func strReverseImpl(ctx *effects.EffContext, args []eval.Value) (eval.Value, error) {
    str := args[0].(*eval.StringValue).Value
    runes := []rune(str)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return &eval.StringValue{Value: string(runes)}, nil
}
```

**Key points:**
- Use `RegisterEffectBuiltin()` in `init()`
- Use Type Builder DSL for type construction
- Include complete metadata (description, params, examples, tags)
- Set `IsPure: true` for pure functions, or `Effect: "IO"` for effects

**See:** [resources/type_builder_examples.md](resources/type_builder_examples.md) for type patterns

### Step 2: Write Hermetic Tests (~1 hour)

**Add tests in** `internal/builtins/register_test.go`:

```go
func TestStrReverse(t *testing.T) {
    ctx := testctx.NewMockEffContext()

    tests := []struct {
        input    string
        expected string
    }{
        {"hello", "olleh"},
        {"", ""},
        {"🎉", "🎉"},
    }

    for _, tt := range tests {
        result, err := strReverseImpl(ctx, []eval.Value{
            testctx.MakeString(tt.input),
        })
        assert.NoError(t, err)
        assert.Equal(t, tt.expected, testctx.GetString(result))
    }
}
```

**Test Harness helpers:**
- `testctx.NewMockEffContext()` - Create test context
- `testctx.MakeString()`, `MakeInt()`, `MakeRecord()` - Create test values
- `testctx.GetString()`, `GetInt()`, `GetRecord()` - Extract values

**For HTTP/FS effects:**
```go
ctx := testctx.NewMockEffContext()
ctx.GrantAll("Net")  // Grant Net capability
ctx.SetHTTPClient(server.Client())  // Use test server
```

**See:** [resources/testing_patterns.md](resources/testing_patterns.md) for more examples

### Step 3: Validate and Inspect (~30 min)

**Run validation:**
```bash
# Validate all builtins
ailang doctor builtins

# List all builtins by module
ailang builtins list --by-module

# Check for orphaned builtins (migration safety)
ailang builtins check-migration
```

**Run tests:**
```bash
# Test specific builtin
go test -v internal/builtins -run TestStrReverse

# Test all builtins
make test
```

**Test in REPL:**
```bash
ailang repl
> :type _str_reverse
string -> string

> _str_reverse("hello")
"olleh" : string
```

### Step 4: Runtime Wiring (Automatic!)

No manual wiring needed! The registry automatically:
- ✅ Registers with evaluator (`internal/eval`)
- ✅ Adds to type environment (`internal/types`)
- ✅ Links to runtime (`internal/runtime`)
- ✅ Generates module interface (`internal/link`)

**Feature flag removed in v0.3.10** - New registry is now the default.

## Key Components

### Central Registry

**Location:** `internal/builtins/spec.go`

**Features:**
- Single registration point with `RegisterEffectBuiltin()`
- Compile-time validation (arity, types, impl, effects)
- Freeze-safe (no registration after init)
- 52 builtins migrated

### Type Builder DSL

**Location:** `internal/types/builder.go`

**Reduces type construction from 35 lines → 10 lines (-71%)**

**Available methods:**
```go
T := types.NewBuilder()

// Primitive types
T.String()
T.Int()
T.Float()
T.Bool()
T.Unit()

// Complex types
T.List(elementType)
T.Record(fields...)
T.Tuple(types...)
T.Con("TypeName")
T.Var("α")

// Function types
T.Func(arg1, arg2, ...).Returns(returnType).Effects("IO", "FS")

// Type applications
T.App("Result", okType, errType)
```

**See:** [resources/type_builder_examples.md](resources/type_builder_examples.md)

### Test Harness

**Location:** `internal/effects/testctx/`

**Value constructors:**
- `MakeString(s string)` → StringValue
- `MakeInt(i int)` → IntValue
- `MakeFloat(f float64)` → FloatValue
- `MakeBool(b bool)` → BoolValue
- `MakeList([]Value)` → ListValue
- `MakeRecord(map[string]Value)` → RecordValue

**Value extractors:**
- `GetString(Value)` → string
- `GetInt(Value)` → int
- `GetFloat(Value)` → float64
- `GetBool(Value)` → bool
- `GetList(Value)` → []Value
- `GetRecord(Value)` → map[string]Value

**Effect mocking:**
- `ctx.GrantAll(effect)` - Grant capabilities
- `ctx.SetHTTPClient(client)` - Mock HTTP
- `ctx.SetFS(fs)` - Mock filesystem

### Validation & Inspection

**Commands:**
```bash
ailang doctor builtins              # Health checks
ailang builtins list                # List all builtins
ailang builtins list --by-module    # Group by module
ailang builtins list --by-effect    # Group by effect
ailang builtins check-migration     # Check for orphaned builtins
```

**6 validation rules:**
1. Type function exists and valid
2. Implementation function exists
3. Arity matches NumArgs
4. Effect consistency (IsPure vs Effect field)
5. Module name valid
6. Metadata complete

## Metrics

**Development time:**
| Task | Before | After | Savings |
|------|--------|-------|---------|
| Files to edit | 4 | 1 | -75% |
| Type construction | 35 LOC | 10 LOC | -71% |
| Total time | 7.5h | 2.5h | -67% |
| Test setup | ~50 LOC | ~15 LOC | -70% |

## Resources

### Detailed Examples

**For type patterns:**
- [resources/type_builder_examples.md](resources/type_builder_examples.md) - All type patterns

**For testing patterns:**
- [resources/testing_patterns.md](resources/testing_patterns.md) - Hermetic test examples

**For comprehensive guide:**
- [M-DX1-FINAL-SUMMARY.md](../../../M-DX1-FINAL-SUMMARY.md) - Complete M-DX1 summary
- `design_docs/planned/easier-ailang-dev.md` - Design rationale
- `CHANGELOG.md` (v0.3.10+) - Version history

### File Locations

**Builtin files by module:**
- `internal/builtins/string.go` - String functions (9 builtins)
- `internal/builtins/math.go` - Math functions (37 builtins)
- `internal/builtins/io.go` - IO functions (3 builtins)
- `internal/builtins/net.go` - Network functions (1 builtin)
- `internal/builtins/show.go` - Show typeclass (1 builtin)
- `internal/builtins/json_decode.go` - JSON decoding (1 builtin)

**Test files:**
- `internal/builtins/*_test.go` - Builtin tests
- `internal/effects/testctx/*_test.go` - Test harness tests

## Common Patterns

### Pattern 0: "Zero-Arg" Builtins (M-DX10 Unit-Argument Model)

**⚠️ AILANG has no true nullary functions!** All functions that appear to take no arguments must take a `unit` parameter.

```go
// ✅ CORRECT - Unit-argument model
RegisterEffectBuiltin(BuiltinSpec{
    Module:  "std/sharedmem",
    Name:    "_sharedmem_keys",
    NumArgs: 1,  // Takes unit parameter!
    Effect:  "SharedMem",
    Type: func() types.Type {
        T := types.NewBuilder()
        return T.Func(T.Unit()).Returns(T.List(T.String())).Effects("SharedMem")
    },
    Impl: func(ctx *effects.EffContext, args []eval.Value) (eval.Value, error) {
        // args[0] is unit, ignored (validates arity)
        keys := ctx.SharedMem.Cache.Keys()
        // ...
    },
})

// ❌ WRONG - True nullary (causes "arity mismatch: 0 vs 1")
RegisterEffectBuiltin(BuiltinSpec{
    Name:    "_sharedmem_keys",
    NumArgs: 0,  // BUG!
})
```

**Stdlib wrapper:**
```ailang
export func keys(u: unit) -> list[string] ! {SharedMem} {
    _sharedmem_keys(u)
}
-- Or expression body:
export func now() -> int ! {Clock} = _clock_now()
```

**Go tests must pass unit:**
```go
// ✅ Correct
result, err := impl(ctx, []eval.Value{&eval.UnitValue{}})

// ❌ Wrong - causes arity mismatch
result, err := impl(ctx, []eval.Value{})
```

**Reference:** [M-DX10 design doc](../../../design_docs/implemented/v0_4_6/m-dx10-nullary-function-calls.md)

### Pattern 1: Pure String Function

```go
RegisterEffectBuiltin(BuiltinSpec{
    Module:  "std/string",
    Name:    "_str_len",
    NumArgs: 1,
    IsPure:  true,
    Type: func() types.Type {
        T := types.NewBuilder()
        return T.Func(T.String()).Returns(T.Int())
    },
    Impl: func(ctx *effects.EffContext, args []eval.Value) (eval.Value, error) {
        s := args[0].(*eval.StringValue).Value
        return &eval.IntValue{Value: len([]rune(s))}, nil
    },
})
```

### Pattern 2: Effect Function with HTTP

```go
RegisterEffectBuiltin(BuiltinSpec{
    Module:  "std/net",
    Name:    "_net_httpRequest",
    NumArgs: 4,
    Effect:  "Net",
    Type:    makeHTTPRequestType,
    Impl:    effects.NetHTTPRequest,  // Uses ctx.GetHTTPClient()
})
```

### Pattern 3: Complex Record Types

**See:** [resources/type_builder_examples.md](resources/type_builder_examples.md) for full example with nested records.

## Progressive Disclosure

This skill loads information progressively:

1. **Always loaded**: This SKILL.md file (workflow overview)
2. **Execute as needed**: Scripts in `scripts/` (validation)
3. **Load on demand**: Detailed type examples in `resources/`

## Notes

- No feature flag needed (default since v0.3.10)
- Registry auto-wires to all subsystems
- Test harness prevents network/FS side effects
- Metadata enables future tooling (docs, LSP)
- Type Builder DSL is type-safe at compile time

Comments (0)

No comments yet. Be the first to comment!