Refactor codebase to be immutable#6
Conversation
WalkthroughThe changes update several Acanthis type classes by introducing constant constructors with optional parameters for operations and asynchronous flags. Methods that previously modified instances now return new immutable instances using Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant I as Instance (AcanthisType)
participant N as New Instance
U->>I: Create instance (const constructor)
U->>I: Call withCheck(check)
I->>N: Return new instance with check added
U->>N: Call withAsyncCheck(asyncCheck)
N->>I: Return new instance with async check applied
U->>I: Call withTransformation(transformation)
I->>N: Return new instance with transformation applied
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
lib/src/types/types.dart (1)
11-13: Use a typedIListfor stronger type safety
While storing all operations in anIListhelps maintain immutability, consider using a generic type parameter (e.g.,IList<AcanthisOperation<O>>) for better compile-time checks and explicitness.- final IList operations; + final IList<AcanthisOperation<O>> operations;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
lib/src/types/boolean.dart(2 hunks)lib/src/types/date.dart(2 hunks)lib/src/types/list.dart(2 hunks)lib/src/types/map.dart(8 hunks)lib/src/types/nullable.dart(2 hunks)lib/src/types/number.dart(2 hunks)lib/src/types/string.dart(6 hunks)lib/src/types/types.dart(9 hunks)lib/src/types/union.dart(2 hunks)pubspec.yaml(1 hunks)test/types/boolean_test.dart(1 hunks)test/types/date_test.dart(1 hunks)test/types/list_test.dart(1 hunks)test/types/nullable_test.dart(1 hunks)test/types/number_test.dart(1 hunks)test/types/pipe_test.dart(1 hunks)test/types/string_test.dart(1 hunks)test/types/union_test.dart(1 hunks)
🔇 Additional comments (102)
test/types/union_test.dart (1)
6-8: Good addition ofconstconstructor test!This test case verifies that
AcanthisUnioncan be instantiated with theconstkeyword, which is essential for the immutability refactoring mentioned in the PR objectives. This aligns perfectly with the goal of transitioning towards an immutable codebase.test/types/date_test.dart (1)
8-10: Great test for immutability support!This test case confirms that
AcanthisDatecan be instantiated with theconstkeyword, which is consistent with the immutability changes being implemented across the codebase. This helps ensure that the class supports compile-time constants.test/types/nullable_test.dart (1)
6-8: Good test for nested immutable types!This test demonstrates that
AcanthisNullablecan be instantiated as a constant with another immutable type (AcanthisDate) as a parameter. This is an excellent addition that ensures the immutability refactoring works with nested type compositions.pubspec.yaml (1)
24-25: Excellent choice of dependencies for immutability!The addition of
fast_immutable_collectionsaligns perfectly with the PR's objective of refactoring the codebase to be immutable. This library provides efficient immutable data structures that will help maintain immutability throughout the codebase.The
metapackage addition is also appropriate, as it provides annotations that can help enforce immutability constraints and improve code documentation.test/types/boolean_test.dart (1)
6-8: LGTM: Good addition of test for const instantiation.This test verifies that
AcanthisBooleancan be created using a const constructor, which is aligned with the PR objective of making the codebase immutable.test/types/string_test.dart (1)
7-9: LGTM: Proper verification of const constructor support.This test correctly verifies that
AcanthisStringsupports constant instantiation, which is consistent with the immutability goals of the PR.test/types/list_test.dart (1)
7-9: LGTM: Good test for const constructor with argument.This test confirms that
AcanthisListcan be instantiated as a constant with anAcanthisDateparameter, aligning with the immutability objectives.test/types/pipe_test.dart (2)
4-6: LGTM: Simple identity function for testing purposes.This helper function serves as a pass-through for DateTime objects, appropriately used in the const constructor test.
10-16: LGTM: Comprehensive const constructor test for AcanthisPipeline.This test properly verifies that
AcanthisPipelinesupports constant instantiation with all required parameters (inType, outType, and transform function). The test is well-structured and uses the previously defined helper function.test/types/number_test.dart (1)
8-10: Good addition - const constructor test enhances coverage.This test case verifies that
AcanthisNumbercan be instantiated with theconstkeyword, supporting the PR's immutability initiative. This aligns perfectly with the PR objective to refactor the codebase for immutability.lib/src/types/boolean.dart (6)
6-9: Excellent conversion to const constructor.The addition of a constant constructor with optional parameters for operations and isAsync supports the immutability refactoring goal while maintaining backward compatibility.
13-16: Good implementation of immutability pattern.Replacing the previous implementation with
withCheckthat returns a new instance rather than modifying the current one follows proper immutability practices.
21-24: Good implementation of immutability pattern.Similar to
isTrue(), this method now correctly returns a new instance with the check applied rather than modifying the existing instance.
37-43: Well-structured implementation of withAsyncCheck.This method correctly creates a new immutable instance with the added async check while setting the isAsync flag to true. The implementation properly leverages immutable collections.
45-50: Clean implementation of withCheck.The method properly creates a new immutable instance with the added check, following the immutability pattern consistently.
52-58: Consistent implementation of withTransformation.This implementation follows the same pattern as the other methods, creating a new immutable instance with the transformation added to operations.
lib/src/types/union.dart (4)
10-10: Clean conversion to const constructor with optional parameters.The constructor now accepts optional parameters for operations and isAsync while maintaining the original elements parameter, providing a good balance of backward compatibility and new functionality.
54-61: Well-implemented withAsyncCheck method.This method correctly creates a new AcanthisUnion instance while preserving the existing elements and adding the new check to operations. Setting isAsync to true is appropriate for async operations.
63-69: Clean implementation of withCheck method.The method follows the same pattern as withAsyncCheck but without setting isAsync, which is appropriate for synchronous checks.
71-77: Consistent implementation of withTransformation.This implementation matches the pattern of the other methods, maintaining consistent behavior throughout the class.
lib/src/types/date.dart (7)
7-10: Good conversion to const constructor.The constructor now properly supports immutability with optional parameters for operations and isAsync, consistent with other type classes in the library.
14-18: Good implementation of min method.The method now returns a new instance with the check applied rather than modifying the existing instance, properly following immutability principles.
23-27: Good implementation of differsFromNow method.Similar to the min method, this implementation now correctly returns a new instance with the check applied.
32-36: Good implementation of max method.This method also correctly returns a new instance with the check applied, maintaining consistency with the other methods.
49-55: Well-structured implementation of withAsyncCheck.This method correctly creates a new AcanthisDate instance with the async check added to operations and isAsync set to true.
57-62: Clean implementation of withCheck.The method properly creates a new immutable instance with the added check, following the immutability pattern consistently.
64-70: Consistent implementation of withTransformation.This implementation maintains the same pattern as the other methods, creating a new immutable instance with the transformation added to operations.
lib/src/types/nullable.dart (4)
11-12: Constructor updated to support immutability pattern.The constructor now uses the
constkeyword and acceptssuper.operationsandsuper.isAsyncparameters, enabling the creation of immutable instances that can still be validated and transformed.
63-69: New method adds async check support while preserving immutability.The
withAsyncCheckmethod properly creates a new instance with the additional check added to the operations collection, maintaining immutability by not modifying the original instance. It also correctly setsisAsyncto true.
71-75: New method adds synchronous check support while preserving immutability.The
withCheckmethod correctly creates a new instance with the additional check, maintaining immutability by not modifying the original instance.
77-82: New method adds transformation support while preserving immutability.The
withTransformationmethod correctly creates a new instance with the additional transformation, maintaining immutability by not modifying the original instance.lib/src/types/list.dart (11)
7-7: Element property made final to ensure immutability.Making the
elementproperty final ensures it cannot be modified after initialization, which supports the immutable codebase objective.
9-9: Constructor updated to support immutability pattern.The constructor now uses the
constkeyword and accepts named parameterssuper.operationsandsuper.isAsync, enabling the creation of immutable instances.
96-99: Method refactored to use immutable pattern.The
minmethod now correctly returns a new instance with the check added, rather than modifying the current instance. This promotes immutability.
104-107: Method refactored to use immutable pattern.The
anyOfmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
112-116: Method refactored to use immutable pattern.The
everyOfmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
121-124: Method refactored to use immutable pattern.The
maxmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
131-134: Method refactored to use immutable pattern.The
uniquemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
139-143: Method refactored to use immutable pattern.The
lengthmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
145-152: New method adds async check support while preserving immutability.The
withAsyncCheckmethod properly creates a new instance with the additional check added to the operations collection. It correctly setsisAsyncto true, which is important for async validation.
154-160: New method adds synchronous check support while preserving immutability.The
withCheckmethod correctly creates a new instance with the additional check, maintaining immutability by not modifying the original instance.
162-169: New method adds transformation support while preserving immutability.The
withTransformationmethod correctly creates a new instance with the additional transformation, maintaining immutability by not modifying the original instance.lib/src/types/number.dart (19)
9-9: Constructor updated to support immutability pattern.The constructor now uses the
constkeyword and accepts optional parameterssuper.isAsyncandsuper.operations, enabling the creation of immutable instances with validation capabilities.
13-16: Method refactored to use immutable pattern.The
ltemethod now correctly returns a new instance with the check added, rather than modifying the current instance. This promotes immutability.
21-24: Method refactored to use immutable pattern.The
gtemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
28-31: Method refactored to use immutable pattern.The
betweenmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
36-39: Method refactored to use immutable pattern.The
gtmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
44-47: Method refactored to use immutable pattern.The
ltmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
52-55: Method refactored to use immutable pattern.The
positivemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
60-63: Method refactored to use immutable pattern.The
negativemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
68-71: Method refactored to use immutable pattern.The
integermethod now correctly returns a new instance with the check added, rather than modifying the current instance.
76-79: Method refactored to use immutable pattern.The
doublemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
84-87: Method refactored to use immutable pattern.The
multipleOfmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
92-95: Method refactored to use immutable pattern.The
finitemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
100-103: Method refactored to use immutable pattern.The
infinitemethod now correctly returns a new instance with the check added, rather than modifying the current instance.
108-111: Method refactored to use immutable pattern.The
nanmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
116-119: Method refactored to use immutable pattern.The
notNaNmethod now correctly returns a new instance with the check added, rather than modifying the current instance.
129-130: Method refactored to use immutable transformation pattern.The
powmethod now correctly returns a new instance with the transformation added, rather than modifying the current instance.
138-141: New method adds async check support while preserving immutability.The
withAsyncCheckmethod properly creates a new instance with the additional check added to the operations collection. It correctly setsisAsyncto true, which is important for async validation.
143-146: New method adds synchronous check support while preserving immutability.The
withCheckmethod correctly creates a new instance with the additional check, maintaining immutability by not modifying the original instance.
148-152: New method adds transformation support while preserving immutability.The
withTransformationmethod correctly creates a new instance with the additional transformation, maintaining immutability by not modifying the original instance.lib/src/types/string.dart (8)
39-39: Constructor updated to support immutability pattern.The constructor now uses the
constkeyword and accepts optional parameterssuper.isAsyncandsuper.operations, enabling the creation of immutable instances with validation capabilities.
43-46: Method refactored to use immutable pattern.The
207-224: Method refactored to use immutable async check pattern.The
uncompromisedmethod now correctly useswithAsyncCheckto create a new instance with the async check added, rather than modifying the current instance. The implementation maintains the same validation logic while adopting the immutable pattern.
359-360: Method refactored to use immutable transformation pattern.The
encodemethod now correctly returns a new instance with the transformation added, rather than modifying the current instance.
365-367: Method refactored to use immutable transformation pattern.The
decodemethod now correctly returns a new instance with the transformation added, rather than modifying the current instance.
392-395: New method adds async check support while preserving immutability.The
withAsyncCheckmethod properly creates a new instance with the additional check added to the operations collection. It correctly setsisAsyncto true, which is important for async validation.
397-400: New method adds synchronous check support while preserving immutability.The
withCheckmethod correctly creates a new instance with the additional check, maintaining immutability by not modifying the original instance.
402-406: New method adds transformation support while preserving immutability.The
withTransformationmethod correctly creates a new instance with the additional transformation, maintaining immutability by not modifying the original instance.lib/src/types/types.dart (11)
1-2: Imports for immutability approach
The addition offast_immutable_collectionsandmetapackages is appropriate for fostering immutability and leveraging@immutableannotations.
8-8: Immutability annotation
Applying@immutabletoAcanthisTypealigns with the immutability goal, clarifying that instances shouldn't be mutated after creation.
16-17: Constructor defaults
SpecifyingoperationsasIList.empty()andisAsyncasfalseby default is consistent with a purely immutable design. This ensures any derived classes or extended types have a safe default.
117-118: Abstract method for adding checks
The newwithCheckmethod signals an immutable pattern: returning a newAcanthisTypeinstead of mutating. This consistently aligns with the rest of the refactoring.
121-122: Async check method
withAsyncCheckneatly parallelswithCheck, ensuring asynchronous operations are handled via a separate pipeline.
133-134: Refine with synchronous checks
Delegating therefinemethod towithCheckelegantly ensures consistency across custom checks.
142-142: Refine with asynchronous checks
Similar torefine,refineAsyncuseswithAsyncCheckto preserve the functional immutable flow.
155-155: IntroducingwithTransformation
Allowing transformations to be appended without mutating the existingAcanthisTypeobject is key to maintaining a purely functional style.
159-159:transformuseswithTransformation
This change ensures thetransformmethod produces a new, updated instance, further cementing immutability.
165-165:@immutablefor all operation classes
MarkingAcanthisCheck,AcanthisAsyncCheck,AcanthisTransformation,AcanthisOperation,AcanthisPipeline, andAcanthisParseResultas@immutableensures every part of the type-checking/transforming workflow remains immutable.Also applies to: 191-191, 217-217, 233-233, 242-242, 321-321
250-250:const AcanthisPipelineconstructor
Declaring the pipeline constructor asconstencourages compile-time instantiation and ensures pipeline objects remain fully immutable.lib/src/types/map.dart (22)
5-6: Imports for immutability
Importingfast_immutable_collectionsandmetasupports the new immutable container usage and annotation strategy.
14-15: Immutable fields and public getter
Changing_fieldsto anIMapand exposing it via an unmodifiable view ensures external code can’t mutate the map contents. Well done.
17-19: Private flags and lists
Keeping_passthrough,_dependencies, and_optionalFieldsas privatefinalfields enforces immutability of these properties, reducing side effects.
21-26: Primary constructor with defaults
Declaring the main constructor asconstand assigning emptyIListdefaults aligns with the move to a fully immutable data structure.
27-37: Private named constructor
IntroducingAcanthisMap._to handle complex initialization while preservingconstusage is a clean approach for building new instances with updated fields.
267-274: Marking fields optional
Using_optionalFields.addAll(fields)returns a new instance, showcasing the functional approach. This pattern is applied throughout, ensuring no in-place modifications occur.
329-337: Adding field dependencies
Appending dependencies via_dependencies.addpreserves the original map’s state while returning a new instance. This is consistent and correct.
352-359:extendto add fields
Creating a new instance withinextendensures immutability.addAll(fields.toIMap())merges extra fields without altering the existing map.
365-365:mergemethod
Deferring toextendsimplifies logic by centralizing the merge implementation into one method.
376-383:pickreturns new instance
Extracting a subset of fields from_fieldsand building a newAcanthisMaphighlights a functional approach to data slicing.
394-401:omitreturns new instance
Similar topick, omitting fields yields an uncluttered approach to removing keys while maintaining immutability.
406-413:passthrough
Switching_passthroughtotruein a new instance avoids side effects on existing map objects. This is exactly how immutable toggles should behave.
432-442:withAsyncCheck
Appending an async check to theoperationslist setsisAsynctotruein the returned map. This design aligns with a purely functional approach to enabling async validations.
444-454:withCheck
Adding a synchronous check in a new map fosters a predictable and safe environment for future transformations or validations.
456-467:withTransformation
As with checks, transformations are appended to theoperationslist without mutating the original object. This seamlessly maintains an event chain for transformations.
472-472:objectfactory
Using.toIMap()ensures that the factory function produces an immutable version of the provided fields. This is consistent with the rest of the design.
475-475: Immutability annotation
Applying@immutableto the private_Dependencyclass is part of the overall immutability focus.
481-481:const _Dependency
By making the constructor constant,_Dependencyinstances also become compile-time constants, preventing runtime mutation.
487-491:const LazyEntryconstructor
This ensures lazy evaluation logic is carried out under an immutable contract, preventing accidental modifications to_typeor inherited fields.
505-512:withAsyncCheckinLazyEntry
Maintaining a consistent approach toisAsyncandoperationsupdates ensures that any lazy type is also fully immutable.
515-521:withCheckinLazyEntry
Similar towithAsyncCheck, the synchronous check is appended without direct mutation. This is a proper extension of theLazyEntryconcept.
523-529:withTransformationinLazyEntry
The transformation is added tooperations, returning another lazy entry instance. Each step remains purely functional.
|
@dickermoshe So I totally agree with these changes, which would allow acanthis validators to be used as parameters in annotations. I am a little concerned about performance. Constantly reinstantiating an object could put a strain on the performance of the application and I think we should investigate this a bit more to avoid slowdowns due to these changes. |
|
I'll write a benchmark for this. Keep in mind that dart is purpose built to make instantiating classes very cheap. A nested widget tree with 1000s of widgets can easier be built in 8ms. Also everything here is Also, this only occurs when creating a validator, not when using it. So validating 10,000 would take the same long as a single one. Also, all forms of functional programming use immutability, so this isn't some sorta hack that we're attempting. However, in systems programming they avoid immutability for this very reason, however they need insane performance, work with with shared memory multi threading and are dealing with large quantities of memory. |
Perfect thank you so much!
I'm aware of it, I just want to be sure that everything is still smooth. 😄 |
Benchmarkimport 'package:acanthis/acanthis.dart' as acanthis;
void makeValidators() {
final boolean = acanthis.boolean().isTrue().isFalse();
final email = acanthis.string().email().min(5).max(20);
final url = acanthis.string().url().min(5).max(20);
final int = acanthis.number().integer();
final double = acanthis.number().double();
final date =
acanthis.date().min(DateTime(2020, 1, 1)).max(DateTime(2023, 1, 1));
}
void main() {
final stopwatch = Stopwatch()..start();
for (final _ in List.generate(100000, (index) => index)) {
makeValidators();
}
stopwatch.stop();
}Hyperfine Results:SummaryIt used to take The next pr will make it quicker than it was originally. |
|
I totally agree with you. I will proceed to merge the pull request. Thank you very much for your time and patience. |
|
I forgot to make |
Description
Acanthis will eventually add a Code Generator for creating datamodels which contain validation.
This is the 1st step in making everything more
@annotationfriendly by making everything const.To aid the process of working with immutable containers, I've added
fast_immutable_collectionswhich is a well maintained package for doing just that.We will need to make the versions constraints of
metaandfast_immutable_collectionsmore flexible before merging.This may be breaking if you consider
addChecka public API . I renamed it towithCheckbecause that's a better description.If this get's merged I will start working on making the built-in checks and transforms into classes.
Fixes: N/A
Type of change
Checklist:
Summary by CodeRabbit
New Features
Refactor
Chores
Tests