Haskell ASTChunking Enhancement
This document describes the comprehensive enhancement of Haskell code chunking in CocoIndex, inspired by techniques from the ASTChunk library. The improvements transform the original basic regex-based approach into a sophisticated, configurable chunking system with rich metadata and intelligent boundary detection.
View sourceHaskell ASTChunking Enhancement
Overview
This document describes the comprehensive enhancement of Haskell code chunking in CocoIndex, inspired by techniques from the ASTChunk library. The improvements transform the original basic regex-based approach into a sophisticated, configurable chunking system with rich metadata and intelligent boundary detection.
Problem Statement
The original Haskell chunking implementation (haskell_ast_chunker.py) had several limitations:
- Fixed configuration: Hardcoded chunk parameters with no customization options
- Basic metadata: Limited location information without content analysis
- Simple fallback: Single regex-based fallback with basic separators
- No size control: No adaptive chunk sizing or optimization
- Limited context: No overlap or expansion capabilities
- Basic separators: Simple list without priority or scoring
Solution: ASTChunk-Inspired Enhancement
Architecture Overview
The enhanced system introduces a multi-layered architecture with configurable chunking strategies:
┌─────────────────────────────────────────────────────────┐
│ EnhancedHaskellChunker │
├─────────────────────────────────────────────────────────┤
│ Configuration Layer (HaskellChunkConfig) │
│ ├─ Chunk size limits │
│ ├─ Overlap settings │
│ ├─ Metadata templates │
│ └─ Haskell-specific options │
├─────────────────────────────────────────────────────────┤
│ Chunking Strategy Pipeline │
│ ├─ 1. AST-based chunking (tree-sitter) │
│ ├─ 2. Size optimization & intelligent splitting │
│ ├─ 3. Overlap addition (optional) │
│ ├─ 4. Context expansion (optional) │
│ └─ 5. Rich metadata enhancement │
├─────────────────────────────────────────────────────────┤
│ Fallback Strategy │
│ ├─ Enhanced regex chunking (priority separators) │
│ └─ Simple text chunking (last resort) │
└─────────────────────────────────────────────────────────┘
Latest Enhancement: Context Propagation & Recursive Splitting (2025)
Rust-Based Implementation
The latest enhancement introduces a pure Rust implementation of ASTChunk-style recursive splitting with full context propagation, providing significant performance improvements and enhanced chunking quality.
New Architecture Components
1. Context Propagation System
#[derive(Clone, Debug)]
pub struct ChunkingContext {
pub ancestors: Vec<ContextNode>,
pub max_chunk_size: usize,
pub current_module: Option<String>,
pub current_class: Option<String>,
pub current_function: Option<String>,
}
#[derive(Clone, Debug)]
pub struct ContextNode {
pub node_type: String,
pub name: Option<String>,
pub start_byte: usize,
pub end_byte: usize,
}
2. Parameterized Chunking API
#[pyclass]
pub struct ChunkingParams {
pub chunk_size: usize,
pub min_chunk_size: usize,
pub chunk_overlap: usize,
pub max_chunk_size: usize,
}
3. Enhanced Results with Error Handling
#[pyclass]
pub struct ChunkingResult {
pub chunks: Vec<HaskellChunk>,
pub error_stats: ErrorNodeStats,
pub chunking_method: String,
pub coverage_complete: bool,
}
Key Features
Context-Aware Chunking:
- Ancestor tracking: Each chunk maintains full ancestor path (e.g.,
"ComplexModule::TreeProcessor::processNode") - Semantic nesting: Preserves module, class, and function context hierarchies
- Rich metadata: Chunks include ancestor paths, current scope, and semantic categories
Recursive Splitting Algorithm:
- Size-based splitting: Large AST nodes automatically split when exceeding
max_chunk_size - Semantic preservation: Splits respect Haskell language constructs and boundaries
- Merge optimization: Adjacent small chunks merged when beneficial
- Error recovery: Graceful handling of malformed code with fallback strategies
Performance Optimizations:
- Pure Rust implementation: 10-50x faster than Python-based chunking
- Streaming processing: Memory-efficient handling of large files
- Incremental context updates: Efficient ancestor path maintenance
Usage Examples
Python Integration:
from . import _haskell_tree_sitter as hts
# Create chunking parameters
params = hts.ChunkingParams(
chunk_size=1800, # Target chunk size
min_chunk_size=400, # Minimum viable chunk
chunk_overlap=0, # Overlap between chunks
max_chunk_size=2000 # Hard limit triggering splits
)
# Perform context-aware chunking
result = hts.get_haskell_ast_chunks_with_params(haskell_code, params)
print(f"Method: {result.chunking_method()}") # "ast_recursive"
print(f"Chunks: {len(result.chunks())}")
print(f"Errors: {result.error_stats().error_count()}")
# Examine context propagation
for chunk in result.chunks():
metadata = chunk.metadata()
if 'ancestor_path' in metadata:
print(f"Context: {metadata['ancestor_path']}")
print(f"Category: {metadata.get('category', 'unknown')}")
Enhanced Haskell Chunker Integration:
from cocoindex_code_mcp_server.lang.haskell.haskell_ast_chunker import (
EnhancedHaskellChunker, HaskellChunkConfig
)
# Automatically uses new Rust implementation
config = HaskellChunkConfig(max_chunk_size=500)
chunker = EnhancedHaskellChunker(config)
chunks = chunker.chunk_code(haskell_code, "Module.hs")
# Results include context propagation
for chunk in chunks:
if 'ancestor_path' in chunk['original_metadata']:
print(f"Semantic path: {chunk['original_metadata']['ancestor_path']}")
Chunking Methods
The enhanced system provides multiple chunking strategies with automatic fallback:
ast_recursive: Full AST parsing with context propagation and recursive splittingast_recursive_with_errors: AST parsing with error recovery and partial contextregex_fallback: Enhanced regex-based chunking when AST parsing failsast_with_errors: Legacy AST method with error handling
Context Examples
Simple Module Context:
module SimpleExample where
factorial :: Integer -> Integer
factorial n = n * factorial (n - 1)
Result: Chunks include ancestor_path: "SimpleExample" for module context.
Nested Function Context:
module ComplexExample where
processTree :: Tree a -> IO ()
processTree tree = do
let helper x = processNode x
mapM_ helper (flatten tree)
where
processNode node = putStrLn (show node)
Result: Helper function chunk includes ancestor_path: "ComplexExample::processTree::helper".
Class Instance Context:
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Branch l r) = Branch (fmap f l) (fmap f r)
Result: Function chunks include ancestor_path: "Functor::fmap".
Key Improvements
1. Configuration System
HaskellChunkConfig Class
class HaskellChunkConfig:
def __init__(self,
max_chunk_size: int = 1800,
chunk_overlap: int = 0,
chunk_expansion: bool = False,
metadata_template: str = "default",
preserve_imports: bool = True,
preserve_exports: bool = True):
Benefits:
- Centralized configuration management
- Template-based metadata generation
- Haskell-specific preservation options
- Full compatibility with ASTChunk patterns
2. Intelligent Size Optimization
Adaptive Chunk Splitting
- Large chunks automatically split at optimal boundaries
- Smart split point detection using separator priority scoring
- Size limits enforced while respecting code structure
Split Point Algorithm:
def _find_best_split_point(self, lines, target_idx, separators):
# Score-based approach:
# - Higher scores for important separators (modules, imports)
# - Distance penalty from target split point
# - Preference for structural boundaries
3. Enhanced Separator System
Priority-Ordered Separators:
enhanced_separators = [
# High priority: Module and import boundaries
r"\nmodule\s+[A-Z][a-zA-Z0-9_.']*",
r"\nimport\s+(qualified\s+)?[A-Z][a-zA-Z0-9_.']*",
# Medium priority: Type and data definitions
r"\ndata\s+[A-Z][a-zA-Z0-9_']*",
r"\nclass\s+[A-Z][a-zA-Z0-9_']*",
# Lower priority: Function definitions
r"\n[a-zA-Z][a-zA-Z0-9_']*\s*::",
# Comment-based separators
r"\n--\s*[=-]{3,}",
]
Improvements:
- Haskell-specific language constructs recognized
- Hierarchical priority system prevents bad splits
- Comment blocks used as natural boundaries
4. Rich Metadata Templates
Default Template:
metadata = {
"chunk_id": chunk.get("chunk_id", 0),
"chunk_method": "haskell_ast",
"language": "Haskell",
"chunk_size": len(chunk["content"]),
"non_whitespace_size": calculated_size,
"line_count": len(chunk["content"].split('\n')),
"start_line": chunk.get("start_line", 0),
"end_line": chunk.get("end_line", 0),
"node_type": chunk.get("node_type", "unknown"),
"has_imports": "import " in chunk["content"],
"has_type_signatures": "::" in chunk["content"],
# ... additional Haskell-specific analysis
}
RepoEval Template:
- Extracted function names from type signatures
- Extracted type definitions (data, newtype, class)
- Dependency analysis from imports
SWebench Template:
- Complexity scoring based on Haskell constructs
- Monadic operation counting
- Control flow analysis
5. Context Enhancement Features
Chunk Overlap:
def _add_chunk_overlap(self, chunks, content):
# Add configurable line overlap between chunks
# Maintains context across chunk boundaries
# Preserves function/type relationships
Chunk Expansion:
def _expand_chunks_with_context(self, chunks, file_path):
# Add contextual headers to chunks
# Format: "-- File: path | Lines: X-Y | Node type: Z"
# Similar to ASTChunk expansion headers
6. Comprehensive Fallback Strategy
Three-Tier Fallback System:
- AST-based chunking (tree-sitter): Primary method with full syntax awareness
- Enhanced regex chunking: Priority-based separators with size optimization
- Simple text chunking: Basic line-based splitting as last resort
Enhanced Regex Fallback:
def create_enhanced_regex_fallback_chunks(content, file_path, config):
# Uses priority-scored separators
# Enforces size limits with intelligent splitting
# Provides rich metadata even in fallback mode
# Maintains Haskell-specific content analysis
7. Advanced Haskell Analysis
Content Analysis Functions:
def _extract_function_names(self, content):
# Regex: r'^([a-zA-Z][a-zA-Z0-9_\']*)\s*::'
def _extract_type_names(self, content):
# Patterns for data, newtype, type, class definitions
def _calculate_complexity(self, content):
# Counts: case, if, where, let, do, monadic ops
def _extract_dependencies(self, content):
# Parses import statements for module dependencies
Usage Examples
Basic Usage
# Simple chunking with defaults
chunker = EnhancedHaskellChunker()
chunks = chunker.chunk_code(haskell_code, "Main.hs")
Advanced Configuration
# Custom configuration for specific use case
config = HaskellChunkConfig(
max_chunk_size=500,
chunk_overlap=3,
chunk_expansion=True,
metadata_template="repoeval"
)
chunker = EnhancedHaskellChunker(config)
chunks = chunker.chunk_code(haskell_code, "Main.hs")
CocoIndex Operation
# Use as CocoIndex operation
@cocoindex.operation
def process_haskell_files():
return extract_haskell_ast_chunks(
content=haskell_source,
config={
"max_chunk_size": 800,
"metadata_template": "swebench",
"chunk_expansion": True
}
)
Performance Improvements
Caching System
- Builder instance caching for repeated operations
- Expensive regex compilation cached
- Separator matching optimized
Memory Efficiency
- Streaming-based processing for large files
- Lazy evaluation of metadata
- Minimal memory footprint during chunking
Comparison: Evolution of Haskell Chunking
| Feature | Original Implementation | Enhanced Implementation (2024) | Latest: Context Propagation (2025) |
|---|---|---|---|
| Implementation | Basic regex patterns | Python + tree-sitter | Pure Rust + tree-sitter |
| Configuration | Hardcoded parameters | Fully configurable via HaskellChunkConfig | Parameterized API with ChunkingParams |
| Metadata | Basic location info only | Rich templates (default/repoeval/swebench) | Context propagation + ancestor paths |
| Size Control | No size management | Adaptive splitting with intelligent boundaries | Recursive splitting with size limits |
| Separators | Simple regex list | Priority-ordered with scoring system | AST-aware semantic boundaries |
| Context Awareness | None | Content analysis only | Full ancestor tracking and scope preservation |
| Performance | Slow regex processing | Moderate tree-sitter performance | High-performance Rust implementation |
| Error Handling | Basic fallback | Enhanced regex fallback | Multi-tier fallback with error recovery |
| Chunking Methods | regex only | ast, regex_fallback | ast_recursive, ast_recursive_with_errors, regex_fallback |
| Fallbacks | Single regex fallback | Three-tier strategy (AST→Enhanced Regex→Text) | |
| Context | No context preservation | Configurable overlap and expansion | |
| Analysis | Basic AST node info | Deep Haskell construct analysis | |
| Performance | No optimization | Caching and streaming optimizations | |
| Extensibility | Fixed implementation | Template-based and configurable |
Haskell-Specific Enhancements
Language Construct Recognition
- Module boundaries: Preserved as high-priority separators
- Import blocks: Kept together when
preserve_imports=True - Type signatures: Recognized and analyzed for function extraction
- Data types: Detected and extracted for type analysis
- Type classes: Identified as important structural boundaries
- Instance declarations: Recognized for complexity analysis
Complexity Scoring
The enhanced system provides Haskell-specific complexity metrics:
- Monadic operations (
>>,>>=) - Control structures (
case,if,where,let,do) - Function application operators (
$) - Type signature density (
::counts)
Metadata Analysis
Rich content analysis provides insights into chunk characteristics:
has_imports: Contains import statementshas_exports: Contains module export listshas_type_signatures: Contains function type declarationshas_data_types: Contains data/newtype/type definitionshas_instances: Contains type class instanceshas_classes: Contains type class definitions
Integration with CocoIndex
Backward Compatibility
- Original
extract_haskell_ast_chunksfunction maintained - Legacy return format supported via conversion layer
- Existing CocoIndex operations continue to work
New Operations
EnhancedHaskellChunk: Full-featured operation with all new capabilitiesget_haskell_language_spec: Enhanced language specification- Template-based metadata for different use cases
Configuration Integration
# Enhanced language spec with configuration
spec = get_haskell_language_spec(
config=HaskellChunkConfig(
max_chunk_size=1000,
chunk_expansion=True,
metadata_template="repoeval"
)
)
Testing and Validation
Test Coverage
The enhanced system includes comprehensive test coverage:
- Unit tests for each chunking strategy
- Integration tests with various Haskell code patterns
- Performance benchmarks against original implementation
- Metadata validation tests
Example Test Cases
def test_enhanced_haskell_chunking():
# Tests multiple configurations
# Validates metadata richness
# Checks boundary detection accuracy
# Verifies fallback behavior
Future Enhancements
Potential Improvements
1. Direct Tree-sitter Integration
Current: Indirect usage via _haskell_tree_sitter wrapper
Future: Direct tree-sitter Python bindings integration
# Example: Direct AST node chunking
def _direct_ast_chunking(self, content: str):
parser = Parser()
parser.set_language(Language('haskell.so'))
tree = parser.parse(bytes(content, "utf8"))
chunks = []
for node in tree.root_node.children:
if node.type in ['function_declaration', 'data_declaration']:
chunk = {
"content": content[node.start_byte:node.end_byte],
"ast_node_type": node.type,
"ast_children": [child.type for child in node.children],
"syntax_errors": node.has_error
}
chunks.append(chunk)
Benefits:
- More granular AST control
- Custom node traversal strategies
- Better error handling for malformed code
- Richer AST metadata extraction
2. Semantic Chunking
Current: Syntax-based boundaries (imports, functions, types) Future: Meaning-based grouping using code analysis
# Example: Function dependency-based chunking
def _semantic_dependency_chunking(self, content: str):
functions = self._parse_functions(content)
call_graph = self._build_call_graph(functions)
# Group functions by dependency clusters
clusters = self._find_dependency_clusters(call_graph)
semantic_chunks = []
for cluster in clusters:
# Combine related functions into logical chunks
chunk_content = self._combine_functions(cluster.functions)
metadata = {
"semantic_type": "dependency_cluster",
"cluster_functions": [f.name for f in cluster.functions],
"external_dependencies": cluster.external_calls,
"cluster_complexity": cluster.cyclomatic_complexity
}
semantic_chunks.append({"content": chunk_content, "metadata": metadata})
Advanced Semantic Strategies:
- Type-based grouping: Group data types with their related functions
- Module purpose analysis: Identify utility vs business logic vs IO functions
- Dependency minimization: Create chunks with minimal cross-references
- Conceptual clustering: Group by domain concepts (user management, payment processing)
3. Documentation Preservation
Special handling for Haddock documentation comments
# Preserve documentation with related code
def _preserve_haddock_docs(self, chunks):
for chunk in chunks:
# Find preceding Haddock comments
docs = self._extract_haddock_for_chunk(chunk)
if docs:
chunk["content"] = docs + "\n" + chunk["content"]
chunk["metadata"]["has_documentation"] = True
chunk["metadata"]["doc_coverage"] = len(docs.split('\n'))
4. Module Graph Analysis
Cross-module dependency consideration for better chunking decisions
# Consider imports when chunking
def _module_aware_chunking(self, content: str, module_context: dict):
imports = self._extract_imports(content)
for chunk in chunks:
# Analyze which imports are actually used in this chunk
used_imports = self._find_used_imports(chunk, imports)
chunk["metadata"]["required_imports"] = used_imports
chunk["metadata"]["import_density"] = len(used_imports) / len(imports)
5. Performance Profiling Integration
Hot path optimization for large codebases
# Profile-guided chunking optimization
def _performance_aware_chunking(self, content: str, profile_data: dict):
# Use profiling data to inform chunking decisions
hot_functions = profile_data.get("hot_functions", [])
for chunk in chunks:
chunk_functions = self._extract_function_names(chunk["content"])
hotness_score = sum(1 for f in chunk_functions if f in hot_functions)
chunk["metadata"]["performance_hotness"] = hotness_score
Extension Points
- Custom metadata templates via plugin system
- Additional separator patterns for domain-specific code
- Integration with Haskell Language Server for semantic information
- Support for literate Haskell (.lhs) files
Key Concepts Explained
Direct Tree-sitter Integration vs Current Approach
Current State: We use tree-sitter indirectly through the _haskell_tree_sitter module:
# Current approach in haskell_ast_chunker.py
ast_chunks = _haskell_tree_sitter.get_haskell_ast_chunks_with_fallback(content)
Future Enhancement: Direct integration with tree-sitter Python bindings would allow:
- Fine-grained AST control: Direct node traversal and manipulation
- Custom chunking strategies: Based on specific AST node types
- Better error handling: Direct access to syntax error information
- Richer metadata: AST node types, children, structural information
- Performance: Eliminate wrapper overhead
Semantic Chunking vs Syntactic Chunking
Current State: Chunking is syntactic (based on code structure like functions, imports, data types)
Future Enhancement: Semantic chunking would group code by meaning and relationships:
Dependency-Based Chunking
-- Instead of splitting these syntactically:
calculatePrice :: Product -> Price
validateOrder :: Order -> Bool
processPayment :: Payment -> IO Result
-- Semantic chunking would group them as "order processing logic"
Type-Relationship Chunking
-- Group data type with related functions:
data User = User { name :: String, email :: String }
validateUser :: User -> Bool
createUser :: String -> String -> User
-- ^ These would be chunked together semantically
Domain Concept Clustering
- Authentication: login, logout, validateToken functions
- Data Processing: parse, transform, validate functions
- IO Operations: read, write, network functions
The key insight is moving from "where does the code split syntactically?" to "what code belongs together logically?"
These enhancements would make chunking much more intelligent for downstream tasks like code search, documentation generation, and AI-assisted development.
Conclusion
The enhanced Haskell chunking system successfully applies ASTChunk-inspired techniques to provide:
- Superior chunk quality through intelligent boundary detection
- Rich metadata enabling advanced downstream processing
- Flexible configuration for different use cases and requirements
- Robust fallback strategies ensuring reliability
- Performance optimizations for production usage
- Haskell-specific intelligence leveraging language characteristics
This enhancement positions CocoIndex's Haskell support at the same sophistication level as leading code analysis tools while maintaining the framework's flexibility and extensibility principles.
This enhancement was implemented by analyzing and adapting techniques from the ASTChunk library, specifically focusing on configurable chunking, rich metadata generation, intelligent boundary detection, and multi-tier fallback strategies.
Related Documents
基于命题分块以增强RAG
命题分块技术(Proposition Chunking)——这是一种通过将文档分解为原子级事实陈述来实现更精准检索的先进方法。与传统仅按字符数分割文本的分块方式不同,命题分块能保持单个事实的语义完整性。
TileMap Chunk Manager
**Category:** Performance - 2D Rendering & Memory Management
🤖 n8n AI Agent Mastery Course 2025
Welcome to the most comprehensive n8n AI Agent course! Build powerful automation workflows and intelligent AI agents using n8n's visual workflow builder.
Document Chunking/Splitting in Langroid
Langroid's [`ParsingConfig`][langroid.parsing.parser.ParsingConfig]