Package selection guidance compiled from public package information, community data, and project usage.
Lens4D is a Dart package that enables the creation of composable, reusable, and type-safe lenses for immutable data structures. It provides a functional programming approach to accessing and updating nested data without mutation, enhancing code clarity and maintainability. Lenses allow developers to focus on specific parts of complex data models, making updates predictable and safe. Ideal for state management in larger applications, Lens4D supports all major platforms including web, mobile, and desktop.
Use cases
Immutable state updates
Nested data manipulation
Functional state management
Reactive UI development
Type-safe data access
Code reuse in data transformations
Key features
Type-safe lens creation
Composable lenses
Immutable data updates
Support for nested structures
Functional programming paradigm
Cross-platform compatibility
Suitable for
Developers using functional programming
State management in large apps
Teams prioritizing type safety
Applications with complex nested data
Frontend developers targeting multiple platforms
Considerations
Requires understanding of functional programming
Learning curve for beginners
Best suited for immutable data patterns
May add overhead in simple use cases
Limited documentation compared to mainstream packages
Original README
Original README snapshot from the linked source repository. Visit the repository for the latest content.
lens4d
A functional lens library for Dart, enabling type-safe, composable data access and modification.
Overview
Lenses are a functional programming pattern that provide a clean, type-safe way to focus on, extract from, and modify deeply nested data structures. Originally from Haskell and popularized in languages like Scala and Kotlin, lenses solve the problem of working with immutable data in an elegant, composable way.
This library is inspired by and adapted from the lens system in http4k Kotlin library, bringing the power of functional lenses to Dart.
Why Lenses?
Working with immutable data often leads to verbose, error-prone code:
// Without lenses - verbose and fragilefinal newUri = uri.replace(
queryParameters: {
...uri.queryParameters,
'search': 'flutter',
'limit': '20',
}
);
With lenses, the same operation becomes clean and composable:
// With lenses - clean and type-safefinal newUri = uri.havingAll([
Query.required('search').of('flutter'),
Query.integer().required('limit').of(20),
]);
Features
✅ Type-safe data access - Extract and modify values with compile-time type safety
✅ Functional composition - Chain operations with having() and havingAll()
✅ Bidirectional lenses - Both read from and write to data structures
✅ Type conversions - Built-in conversions for integers, booleans, dates, and more
✅ Error handling - Comprehensive failure handling with detailed error information
✅ Zero dependencies - Pure Dart implementation
Quick Start
Installation
Add lens4d to your pubspec.yaml:
dependencies:lens4d:^1.0.0
Basic Usage
Here's a practical example working with URI query parameters:
import'package:lens4d/lens4d.dart';
/// Define a Query lens for Uri objectsfinalQuery = BiDiLensSpec<Uri, String>(
'query',
constStringParam(),
LensGet<Uri, String>((name, uri) {
final value = uri.queryParameters[name];
return value != null ? [value] : [];
}),
LensSet<Uri, String>((name, values, uri) {
final newParams = Map<String, String>.from(uri.queryParameters);
if (values.isEmpty) {
newParams.remove(name);
} else {
newParams[name] = values.first;
}
return uri.replace(queryParameters: newParams);
}),
);
voidmain() {
final uri = Uri.parse('https://api.example.com/search?q=dart&limit=10');
// Extract values with type safetyfinal searchTerm = Query.required('q')(uri); // 'dart'final limit = Query.integer().required('limit')(uri); // 10 (as int)final page = Query.integer().optional('page')(uri); // null// Single modificationfinal newUri = uri.having(Query.required('q').of('flutter'));
// Multiple modificationsfinal complexUri = uri.havingAll([
Query.required('search').of('dart programming'),
Query.integer().required('limit').of(50),
Query.boolean().required('debug').of(true),
]);
// Error handlingtry {
Query.required('missing')(uri);
} onLensFailurecatch (e) {
print('Parameter not found: ${e.failures.first}');
}
}
Type Conversions
Lenses support automatic type conversions, which are extensible via extension methods:
The having() and havingAll() extension methods enable clean functional composition:
// Chain single modificationsfinal result = data
.having(someLens.of(value1))
.having(anotherLens.of(value2));
// Apply multiple modifications at oncefinal result = data.havingAll([
lens1.of(value1),
lens2.of(value2),
lens3.of(value3),
]);
API Overview
BiDiLensSpec<IN, OUT> - Bidirectional lens specification for extraction and injection
LensSpec<IN, OUT> - Unidirectional lens for extraction only