# Stellarmorphism
A stellar-themed algebraic data type DSL for Elixir that brings type-safe, elegant pattern matching and construction to your applications. Stellarmorphism provides a beautiful syntax for defining sum and product types with powerful recursion patterns.
## Features
### Phase 0: Core Foundation
- **Planets** (`defplanet`) - Product types (structs) with `orbitals` (fields)
- **Stars** (`defstar`) - Sum types with `layers` containing `core` variants
- **Fission** - Type-safe pattern matching with star-prefixed syntax
- **Fusion** - Type-safe construction with star-prefixed syntax
- **Asteroids** - Lightweight recursive identifiers and helpers
### Phase 1: Advanced Recursion
- **Parameterized Types** - Generic types with constraints
- **Asteroid Recursion** - Eager evaluation for immediate computation
- **Rocket Recursion** - Lazy evaluation for deferred/infinite structures
- **Enhanced Constructors** - Type parameters with runtime validation
- **Mixed Recursion** - Combine eager and lazy patterns in same structure
## Installation
Add `stellarmorphism` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:stellarmorphism, "~> 1.0"}
]
end
```
Then add to your modules:
```elixir
defmodule MyApp.Types do
use Stellarmorphism
end
```
## Quick Start
### Basic Planets (Product Types)
```elixir
defplanet User do
orbitals do
moon id :: String.t()
moon name :: String.t()
moon email :: String.t()
moon score :: integer()
end
end
# Usage
user = User.new(%{id: "1", name: "Alice", email: "alice@example.com", score: 100})
```
### Basic Stars (Sum Types)
```elixir
defstar Result do
layers do
core Success, value :: any()
core Error, message :: String.t(), code :: integer()
end
end
# Construction
success = core(Success, value: "Operation completed")
error = core(Error, message: "Not found", code: 404)
```
### Fission (Pattern Matching)
```elixir
message = fission Result, result do
core Success, value: data -> "Got: #{inspect(data)}"
core Error, message: msg, code: code -> "Error #{code}: #{msg}"
end
```
### Fusion (Construction)
```elixir
result = fusion Result, response do
{:ok, data} -> core Success, value: data
{:error, reason} -> core Error, message: reason, code: 500
end
```
## Phase 1: Advanced Recursion
### Binary Tree with Asteroid Recursion (Eager)
```elixir
defstar BinaryTree do
layers do
core Empty
core Leaf, value :: any()
core Node,
left :: asteroid(BinaryTree),
right :: asteroid(BinaryTree),
data :: any()
end
end
# Build tree - all nodes computed immediately
tree = core(Node,
left: asteroid(core(Leaf, value: 1)),
right: asteroid(core(Leaf, value: 3)),
data: 2
)
# Direct access (no function calls needed)
left_value = tree[:left][:value] # => 1
```
### Lazy Stream with Rocket Recursion (Lazy)
```elixir
defstar LazyStream do
layers do
core Empty
core Cons,
head :: any(),
tail :: rocket(LazyStream)
end
end
# Build lazy stream - tail computed on demand
stream = core(Cons,
head: 1,
tail: rocket(fn ->
core(Cons,
head: 2,
tail: rocket(fn -> core(Empty) end)
)
end)
)
# Launch rockets to access values
tail_stream = launch(stream[:tail])
second_value = tail_stream[:head] # => 2
```
### Mixed Recursion Patterns
```elixir
defstar HybridTree do
layers do
core Empty
core EagerNode,
value :: any(),
children :: list() # Immediate computation
core LazyNode,
value :: any(),
children :: rocket(list()) # Deferred computation
end
end
# Eager node - children computed immediately
eager = core(EagerNode,
value: "root",
children: [
asteroid(core(EagerNode, value: "child1", children: [])),
asteroid(core(EagerNode, value: "child2", children: []))
]
)
# Lazy node - children computed on demand
lazy = core(LazyNode,
value: "root",
children: rocket(fn ->
[core(LazyNode, value: "child1", children: rocket(fn -> [] end))]
end)
)
# Access patterns
eager_children = eager[:children] # Direct access
lazy_children = launch(lazy[:children]) # Launch required
```
## Type Safety Benefits
Stellarmorphism eliminates namespace collisions by requiring star-prefixed syntax:
```elixir
# Multiple stars can safely use same core names
defstar DatabaseResult do
layers do
core Success, rows :: list(), count :: integer()
core Error, message :: String.t(), sql_code :: String.t()
end
end
defstar HttpResult do
layers do
core Success, body :: String.t(), headers :: map()
core Error, message :: String.t(), http_code :: integer()
end
end
# Each star type is completely independent
db_result = core(Success, rows: data, count: 10)
http_result = core(Success, body: "response", headers: %{})
# Pattern matching with explicit star types
db_msg = fission DatabaseResult, db_result do
core Success, rows: rows, count: count -> "Found #{count} rows"
core Error, message: msg, sql_code: code -> "DB Error #{code}: #{msg}"
end
http_msg = fission HttpResult, http_result do
core Success, body: body, headers: _headers -> "Response: #{body}"
core Error, message: msg, http_code: code -> "HTTP Error #{code}: #{msg}"
end
```
## Parameterized Types & Constraints
```elixir
defstar BoundedList(max_size) when is_integer(max_size) and max_size > 0 do
layers do
core Empty, capacity :: integer()
core Partial,
items :: list(),
count :: integer(),
capacity :: integer()
core Full,
items :: list(),
capacity :: integer()
end
end
# Type constraint validation at construction
{:ok, small_list_type} = Types.apply_type_params(
BoundedList,
[{:max_size, quote(do: is_integer(max_size) and max_size > 0)}],
[5]
)
{:error, _} = Types.apply_type_params(
BoundedList,
[{:max_size, quote(do: is_integer(max_size) and max_size > 0)}],
[-1] # Invalid: negative size
)
```
## Performance Characteristics
### Asteroid vs Rocket Trade-offs
**Asteroids (Eager Evaluation):**
- ✅ Higher memory usage upfront
- ✅ Faster access (no function calls)
- ✅ Immediate computation
- ❌ Not suitable for infinite structures
**Rockets (Lazy Evaluation):**
- ✅ Lower memory until launched
- ✅ Supports infinite/large structures
- ✅ Deferred expensive computations
- ❌ Slower access (requires function calls)
```elixir
# Asteroid: All computed immediately
eager_tree = core(Node,
left: asteroid(expensive_computation()),
right: asteroid(another_computation()),
data: "root"
)
# Rocket: Computed only when needed
lazy_tree = core(Node,
left: rocket(fn -> expensive_computation() end),
right: rocket(fn -> another_computation() end),
data: "root"
)
# Access patterns
eager_left = eager_tree[:left] # Immediate
lazy_left = launch(lazy_tree[:left]) # Computed now
```
## Benchmarks
Stellarmorphism includes comprehensive performance benchmarks to help you understand the trade-offs between asteroid (eager) and rocket (lazy) recursion patterns. The benchmark suite tests everything from basic performance to concurrency scaling and real-world scenarios.
### Running Benchmarks
```bash
# Install dependencies first
mix deps.get
# Memory-safe benchmarks (recommended - prevents OOM)
mix run benchmarks/memory_safe_bench.ex
# Quick performance tests
mix run benchmarks/quick_bench.ex
# Run specific memory-safe tests
mix run benchmarks/memory_safe_bench.ex construction # Safe construction test
mix run benchmarks/memory_safe_bench.ex memory # Safe memory analysis
mix run benchmarks/memory_safe_bench.ex progressive # Progressive scale test
mix run benchmarks/memory_safe_bench.ex limits # Show safe limits
# Individual quick tests
mix run benchmarks/quick_bench.ex construction # Construction performance
mix run benchmarks/quick_bench.ex access # Access patterns
mix run benchmarks/quick_bench.ex traversal # Tree traversal
mix run benchmarks/quick_bench.ex memory # Memory usage
mix run benchmarks/quick_bench.ex comparison # Direct comparison
# Simple test to verify everything works
mix run benchmarks/simple_benchmark.ex simple
```
The benchmark suite demonstrates key performance characteristics:
**Construction Performance**: Asteroids build structures immediately while rockets defer computation
**Access Patterns**: Direct asteroid access vs rocket launch() overhead
**Memory Usage**: Asteroids use more upfront memory, rockets scale better (⚠️ exponential growth at scale)
**Traversal Operations**: Full structure processing comparisons
**Memory Safety**: Binary trees grow exponentially (2^depth nodes). Use memory-safe benchmarks to prevent out-of-memory conditions.
### Benchmark Categories
#### 🔥 Asteroid vs Rocket Performance
Tests the fundamental performance differences between eager and lazy evaluation:
- **Construction Performance**: Time to build data structures
- **Access Patterns**: Direct access vs launch() overhead
- **Traversal Performance**: Full structure processing
- **Memory Usage**: Memory consumption patterns
- **Evaluation Strategies**: Partial vs full evaluation
#### ⚡ Concurrency Performance
Tests performance scaling from 1 to 32 processes:
- **Construction Concurrency**: Building structures in parallel
- **Traversal Concurrency**: Processing structures concurrently
- **Pattern Matching**: Concurrent fission operations
- **Mixed Workloads**: Real-world concurrent scenarios
- **Rocket Evaluation**: Lazy evaluation under concurrent load
#### 📈 Scale Performance
Tests performance as data structure sizes grow:
- **Tree Scaling**: Binary trees from depth 3 to 15
- **Stream Scaling**: Lazy streams from 10 to 10,000 elements
- **Memory Scaling**: Memory usage analysis at scale
- **Workload Scaling**: Batch operations from 100 to 5,000 items
- **Performance Degradation**: Analysis of scaling bottlenecks
#### 🏗️ Composite Real-World Scenarios
Tests realistic usage patterns:
- **JSON Processing**: Parsing and transforming nested JSON
- **Error Handling Pipelines**: Result types in processing chains
- **Data Transformation**: ETL-style workflows
- **Caching Simulation**: Lazy evaluation for cache systems
- **Parser Combinators**: Building and evaluating parse trees
- **Web API Simulation**: Request/response processing
### Performance Characteristics
The benchmarks reveal key performance trade-offs:
**Asteroid (Eager Evaluation):**
- ✅ Faster access (no function calls)
- ✅ Predictable memory usage
- ✅ Better for frequently accessed data
- ❌ Higher upfront memory cost
- ❌ Not suitable for infinite structures
- ❌ All computation done immediately
**Rocket (Lazy Evaluation):**
- ✅ Lower initial memory usage
- ✅ Supports infinite/large structures
- ✅ Computation only when needed
- ✅ Better for streaming scenarios
- ❌ Slower access (requires launch())
- ❌ Unpredictable evaluation timing
### Benchmark Results
Results are saved as HTML reports in `benchmarks/results/` with:
- Detailed performance metrics
- Memory usage analysis
- Concurrency scaling charts
- Performance comparisons
- System configuration details
### Performance Guidelines
Based on benchmark results:
1. **Use asteroids when**: You need frequent access, bounded data, predictable performance
2. **Use rockets when**: You have large/infinite data, infrequent access, streaming use cases
3. **Concurrency**: Optimal performance typically at 4-8 processes for CPU-bound tasks
4. **Memory**: Monitor usage carefully for deep structures (2^depth growth)
5. **Hybrid approaches**: Combine both patterns based on access patterns
## Utility Functions
### Deep Evaluation
```elixir
# Evaluate all nested rockets in a structure
nested_rockets = %{
level1: rocket(fn ->
%{level2: rocket(fn -> "deep_value" end)}
end)
}
fully_evaluated = Recursion.deep_launch(nested_rockets)
# => %{level1: %{level2: "deep_value"}}
```
### Rocket Depth Analysis
```elixir
# Count nesting levels without evaluation
depth = Recursion.rocket_depth(nested_rockets) # => 2
```
### Type Information
```elixir
# Check if a module uses parameterized types
Registry.is_parameterized?(MyBinaryTree) # => true
# Get type parameters
params = Registry.get_type_params(MyBinaryTree)
# => [{:t, nil}]
```
## Migration & Compatibility
Phase 0 code continues to work unchanged. You can gradually adopt Phase 1 features:
```elixir
# Mix old and new approaches
mixed_structure = %{
legacy_field: "old_style",
eager_recursive: asteroid(%{data: "eager"}),
lazy_recursive: rocket(fn -> %{data: "lazy"} end)
}
```
## API Reference
### Core Macros
- `defplanet/2` - Define product types with orbitals
- `defstar/2` - Define sum types with layers
- `fusion/3` - Type-safe construction with pattern matching
- `fission/3` - Type-safe pattern matching
- `core/1`, `core/2` - Construct star variants
- `asteroid/0`, `asteroid/1` - Eager recursion helpers
- `rocket/1` - Create lazy evaluation structures
- `launch/1` - Evaluate rocket structures
### Helper Modules
- `Stellarmorphism.Types` - Type parameter extraction and validation
- `Stellarmorphism.Recursion` - Asteroid/rocket utilities
- `Stellarmorphism.Constructors` - Enhanced constructor generation
- `Stellarmorphism.Registry` - Type registration and metadata
## Examples
### Real-world JSON Parser
```elixir
defstar JsonValue do
layers do
core Null
core Bool, value :: boolean()
core Number, value :: number()
core String, value :: String.t()
core Array, elements :: list()
core Object, fields :: map()
end
end
# Parse with pattern matching
parse_json = fn input ->
fusion JsonValue, input do
nil -> core(Null)
bool when is_boolean(bool) -> core(Bool, value: bool)
num when is_number(num) -> core(Number, value: num)
str when is_binary(str) -> core(String, value: str)
list when is_list(list) -> core(Array, elements: list)
map when is_map(map) -> core(Object, fields: map)
end
end
# Use with fission
stringify = fn json_value ->
fission JsonValue, json_value do
core Null -> "null"
core Bool, value: true -> "true"
core Bool, value: false -> "false"
core Number, value: num -> to_string(num)
core String, value: str -> "\"#{str}\""
core Array, elements: elements -> "[#{Enum.join(elements, ", ")}]"
core Object, fields: fields -> "{#{inspect(fields)}}"
end
end
```
### Functional Data Structures
```elixir
# Persistent list with structural sharing
defstar PersistentList do
layers do
core Empty
core Cons,
head :: any(),
tail :: asteroid(PersistentList)
end
end
# Infinite sequence generator
defstar InfiniteSeq do
layers do
core Generator,
current :: any(),
next :: rocket(InfiniteSeq)
end
end
# Fibonacci sequence
fibonacci = fn ->
fib = fn a, b ->
core(Generator,
current: a,
next: rocket(fn -> fib.(b, a + b) end)
)
end
fib.(0, 1)
end
# Take first n elements
take = fn seq, n ->
if n <= 0 do
[]
else
case seq do
core(Generator, current: current, next: next_rocket) ->
[current | take.(launch(next_rocket), n - 1)]
end
end
end
fibs = fibonacci.()
first_10 = take.(fibs, 10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
```
## Contributing
Contributions welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
## License
MIT License - see LICENSE file for details.