deranged 0.1.0
deranged: ^0.1.0 copied to clipboard
Generic range and progression types, inspired by Rust and Kotlin
Changelog #
All notable changes to this project will be documented in this file.
This project adheres to Semantic Versioning.
0.1.0 · 2026-08-11 #
⚠️ BREAKING CHANGES #
RangeTo changed meaning.
Naming now follows one convention throughout: until means an exclusive end, to means an inclusive end, in class names as well as method names. Previously, RangeTo(5) was ..<5, while x.rangeTo(5) was x..=5, i.e., to meant opposite things depending on where you read it. (4561e4a)
| Old | New | Meaning |
|---|---|---|
RangeTo |
RangeUntil |
..<x |
RangeToInclusive |
RangeTo |
..=x |
IntRangeTo |
IntRangeUntil |
..<x |
DoubleRangeTo |
DoubleRangeUntil |
..<x |
DoubleRangeToInclusive |
DoubleRangeTo |
..=x |
⚠️ RangeTo and DoubleRangeTo are reused rather than retired, so existing code that names them still compiles but now builds an inclusive range. Search your code for RangeTo before upgrading. The x.rangeUntil(…)/x.rangeTo(…) methods are unchanged.
Matching factory renames on RangeBounds: RangeBounds.to(…) → RangeBounds.until(…) and RangeBounds.toInclusive(…) → RangeBounds.to(…). (4561e4a, 41995a3)
Other breaking changes:
- Every extension declaration was renamed from a
…Extensionsuffix to aDeranged…prefix:IntExtension→DerangedInt,ComparableExtension→DerangedComparable,RangeOfStepExtension→DerangedRangeOfStep, and so on. (715a4bb) Stepwas split intoStepandStepUnlimited, and both became mixins instead of anabstract interface class.Step's stepping methods returnnullwhen the step can't be taken, whileStepUnlimited(for types likeintthat can always step) narrows them to non-nullable returns. (2456dad,cd171ce)Progression.endwas renamed toProgression.endInclusive. The name said exclusive whilelength,last,containsandtoStringall treated it as inclusive. AffectsIntProgressionandStepProgression. (4561e4a)- The bound-conversion getters on
RangeBoundsdropped theirAsinfix:startAsInclusive→startInclusive,startAsExclusive→startExclusive,endAsInclusive→endInclusive,endAsExclusive→endExclusive. They now match the names the concrete range classes already used. (eabe3e2) IntRangeno longer implementsIntProgression. The two used different==implementations, which made equality asymmetric (IntProgression(0, 3, 1) == IntRange(0, 3)wastrue, the reversefalse).IntRange.stepwas removed along with it; useIntRange.stepBy(…)to get anIntProgression. (9e25702)IntRangeUntil.contains(…)(formerlyIntRangeTo) now excludes its end, matching its exclusiveendBoundand its..<endrepresentation. (f5cba63)DoubleRange.contains(…)now excludes its end, matching its exclusiveendBound. (f5cba63)double.rangeTo(…)now returns aDoubleRangeInclusiveinstead of the exclusiveDoubleRange, matching its documented inclusive semantics. (f5cba63)- The
endExclusivegetters on the two unbounded-startdoubleranges were removed – they subtracted1from adoubleend, which is meaningless. (f5cba63) IntRangeFrom.lengthnow throws anUnsupportedErrorinstead of looping forever. (f5cba63)- Removed
rangeToWithLength(…)from bothDerangedStepandDerangedStepUnlimited. A range's length is onlyend - startwhen the end is exclusive, so a closed range built from a length containedlength + 1values – the name did not match the result. UserangeUntilWithLength(…), followed by.inclusiveif you need aRangeInclusive. (9d9c323) - Renamed
int.rangeWithLength(…)toint.rangeUntilWithLength(…), matchingDerangedStepand making the exclusive end explicit. (bdfb268) RangeInclusive.operator |was the smallest range covering both operands, bridging any gap between them.|is now the true union on all ofRangeBoundsand returns aRangeSet, so gaps survive. The old behavior moved toRangeBounds.span(…), which returns anAnyRange. ⚠️ Both spellings still compile where the result is only passed on, so check any|on ranges when upgrading. (7a923c6,f199321)Iterable<RangeInclusive>.unionwas renamed to.span, matching the above, and still returns aRangeInclusive?. For the true union, use.asRangeSet. (f199321)RangeBoundsandRangeSetnow share aRangeLikebase class, which carries the set algebra.|,&,-,~,contains(…),containsAll(…),intersects(…),isEmpty,isFull,bounds,span(…),asRangeSet,mapBounds(…), andcastBounds(…)all live there, so they accept and mix ranges and sets freely:~someRange,range | set, andset & rangeall work now. (f199321)RangeBounds.isUnboundedwas renamed toisFull. It means "describes every value", which for a set isn't the same as being unbounded:{..<0, 5..}has no bound in either direction yet is missing everything in between. The old name also collided withBound.isUnbounded, which is about a single bound. (f199321)RangeBounds.containsRange(…)andRangeSet.containsRange(…)were renamed tocontainsAll(…), since they now accept a set as well as a range.RangeBounds.containsAll(…)also returnstruefor an empty argument, which the oldcontainsRange(…)got wrong. (f199321)RangeInclusive.operator &returned a preciseRangeInclusive?;&now comes fromRangeLikeand returns aRangeSet. The precise pairwise form isRangeBounds.intersect(…), which returns anAnyRangethat is empty when the ranges are disjoint. It pairs withspan(…)exactly as&pairs with|. (f199321)RangeSet.span(the getter for the covering range) was renamed tobounds, freeingspanto mean the same thing everywhere:a.span(b)is the smallest range covering both, and now works between ranges and sets in either direction. (f199321)RangeBoundsAsRangeSetExtensionis gone;asRangeSetis aRangeLikemember.IterableOfRangeBoundsExtensionbecameDerangedIterableOfRangeLike, andRangeSet.of(…)/RangeSet.single(…)accept sets as well as ranges. (f199321,715a4bb)- The range codecs take a single optional positional
innerCodecinstead of theinnerCodec+encodeInner+decodeInnernamed triple, which could previously be combined in ways anasserthad to reject.RangeAsMapCodec(innerCodec: c)becomesRangeAsMapCodec(c); to pass functions, wrap them in the newFunctionBasedCodec. (22e6570)
🎉 New Features #
Ranges
- Added the
IntRange.inclusive(start, endInclusive)constructor.intis discrete, so half-open and closed ranges represent the same values;IntRangestays the single canonical half-open type and this constructor covers the inclusive spelling.int.rangeTo(…)now delegates to it. (9e25702) - Added
isEmptyandisNotEmptytoRangeBounds, so a range can be tested without materializing its values. (93f1bf7) - Added
isSingle, which reports whether a range describes exactly one value, first toRangeInclusive(8faa051) and then toRange(forStepvalues),IntRange, andDoubleRangeInclusive(87ceefe). - Added
clamp(…), which limits a value to a range. It's available wherever it's well-defined: on anyRangeBoundswhose values implementStep, onRangeInclusive/RangeFrom/RangeTofor anyComparable, and on theint/doubleequivalents. It's deliberately absent fromDoubleRangeandDoubleRangeUntil, since there is no largestdoublebelow an exclusive end. (39962f3) - Added
mapBounds(…)andcastBounds(…)onRangeBounds, which convert the bound values while preserving the range's shape. They aren't calledmap/castbecauseIntRange& co. also implementIterable, where those names mean mapping the range's elements. Theintanddoubleranges narrowmapBounds(…), so the mapper receives anintordoublerather than anum. (8d9d75f) - Added
shift(…), which moves a range's bounds by an offset. OnSteptypes it returnsnullif a bound can't be stepped that far; onStepUnlimited,int, anddoubletypes it's non-nullable. (eadb75f) - Added
copyWith(…)on the ranges with both a start and an end (Range,RangeInclusive,IntRange,DoubleRange,DoubleRangeInclusive), which narrow it to their own return and parameter types. Single-bound ranges don't have it –RangeFrom(newStart)is already as short as acopyWithcall. (5ddd4c4) - Added
copyWithBounds(…)onRangeBounds, which replaces wholeBounds rather than their values. It returns anAnyRange, since replacing a bound can change the range's shape. (5ddd4c4) - Added
reversetoRangeandRangeInclusiveofStepvalues (b9632d6,5c4686f), then toIntRange,IntProgression, andStepProgression(e5e9b3b). A progression'sreversestarts at itslastvalue rather than itsendInclusive, so both contain exactly the same values. - Added factory constructors on
RangeBounds–.full(),.from(…),.until(…),.to(…),.inclusiveOrUnbounded(…),.exclusiveOrUnbounded(…)– so ranges can be written with dot shorthands, and made themconstwhere possible. (41995a3,c4780f1,d0783f7) - Added
AnyRange.inclusiveOrUnbounded(…)andAnyRange.exclusiveOrUnbounded(…), which build a range from nullable bound values. (f6b4d41) RangeBoundsandProgressionnow override==,hashCode, andtoString(). (a6ac502)
Iteration
- Added
.iteron ranges ofStepvalues, which iterates the range one value at a time. (6f8f212) - Added
stepBy(…)on ranges ofStepvalues, producing aStepProgression. (6d7a1cd) - Added
IntProgression.stepBy(…), for parity withStepProgression.stepBy(…). (e5e9b3b) - Added
lengthto the iterable ranges ofStepvalues. (61d3d25) - Added
operator []to the iterable ranges and toProgression, as a shorthand forelementAt(…). (18df627,899f4e1) - Added
rangeUntilWithLength(…)onintand onStep/StepUnlimitedvalues, which builds a range from a start and a length. (3ee643d,d3623a0)
Step and Bound
- Added
StepUnlimited, the counterpart ofStepfor types that can always take a step, along with theDerangedStepandDerangedStepUnlimitedextensions carryingprevious,next, and the range-building helpers. (2456dad,1ff8e26,2d00eb1) - Added inclusive/exclusive bound conversion for ranges of
Stepvalues: thestartInclusive,startExclusive,endInclusive, andendExclusivegetters, plus the.inclusive/.exclusiveconversions betweenRangeandRangeInclusive. (27e52ed,43a0ccc,8d79ef6) - Added
Boundfactory constructors –.inclusive(…),.exclusive(…),.unbounded(),.inclusiveOrUnbounded(…),.exclusiveOrUnbounded(…)– usable with dot shorthands. (71f6d48,c4780f1) - Added
Bound.valueOrNull(d47b002),Bound.map(…)/Bound.cast(…)(171d952), and==/hashCodeoverrides (94c5898). - Added bound-level helpers on
Bound, which the range and set logic is built from: the staticBound.compareStarts(…)/Bound.compareEnds(…)order two bounds by where a range starting/ending there begins/stops,Bound.laterStart(…)/Bound.earlierEnd(…)pick the narrower of two,Bound.adjoins(end, start)reports whether two ranges meet without a gap, andBound.invertedturns a start bound into the end bound of everything before it and vice versa. (78aa979,24a9dbc)
Set operations
- Added
RangeSet, a normalized set of disjoint ranges, with the full set algebra spelled as the bitwise operators –|union,&intersection,-difference, and~complement – pluscontains(…),containsAll(…),intersects(…),bounds, andmapBounds(…). It storesAnyRanges, so unbounded and exclusive bounds work throughout, which is what makes~expressible. Normalization drops empty ranges, merges overlapping and adjoining ones, and sorts the rest, so two sets are equal exactly when they describe the same values. Since the ranges stay sorted,|and&are a single linear sweep rather than a re-sort. (7a923c6) - Added
RangeSet.coalescedBy(…), which merges ranges with no values between them. Normalization only compares bound values, so it keeps{0..=4, 5..=9}apart;coalescedBy(…)takes the successor function that decides such cases.DerangedRangeSetOfStep.coalescedderives it from aSteptype, andDerangedRangeSetOfInt.coalescedAsIntscoversintsets – spelled out because aRangeSet<num>can just as well holddoubleranges, which must not be stepped by one. (7a923c6) - Added
RangeBounds.asRangeSetandIterable<RangeBounds>.asRangeSet, andRangeSetAsListCodec. (7a923c6) RangeSetgainedisFull, the counterpart ofisEmpty: whether it describes every value. A set is full exactly when it normalized down to a single full range, so a set with a gap is not full even though itsboundsare. (f199321)- Added the pairwise
|and&onRangeInclusive,&on ranges ofStepvalues, andintersectRangeBounds(…), ahead of the generalRangeLikealgebra. (715dcb2,759832b,d38153c) - Added
DerangedIterableOfRangeInclusive, withspanandintersectionover an iterable of ranges. (aeb981b)
Serialization
- Added codecs for the range and progression types:
RangeAsMapCodec,RangeInclusiveAsMapCodec,IntRangeAsMapCodec,DoubleRangeAsMapCodec,RangeFromAsMapCodec,RangeUntilAsMapCodec,RangeToAsMapCodec,DoubleRangeInclusiveAsMapCodec,StepProgressionAsMapCodec, andIntProgressionAsMapCodec. (101342b,44ebd4d,8a20a7a,d46b2ad) - Added
BoundAsMapCodecandAnyRangeAsMapCodec. Unlike the other range codecs, these round-trip the kind of each bound alongside its value ({"type": "inclusive", "value": …}), so every range shape can be serialized. (d46b2ad) - Every codec takes an optional
innerCodecfor the bound values, so ranges of custom types can be serialized. (4690fbb) - Added
FunctionBasedCodec, so aninnerCodeccan be supplied without declaring aCodecclass. (22e6570) - Exported the
StartEndAsMapCodec,SingleBoundAsMapCodec, andProgressionAsMapCodecbase classes, so custom codecs can reuse them.CodecAndJsonConverterandAsMapCodecremain internal. (d46b2ad,22e6570)
🐛 Bug Fixes #
double.rangeUntil(…)no longer subtracts1from the end. (f5cba63)IntRange.stepBy(…)now passes its inclusive end toIntProgression, whose end is inclusive.0.rangeTo(9).stepBy(2)returned0, 2, 4, 6, 8, 10and now returns0, 2, 4, 6, 8. (f5cba63)IntRangeFrom.elementAt(…)no longer hangs on negative indices; it throws aRangeError. (f5cba63)Range.length,RangeInclusive.length, andIntRange.lengthnow return0for empty ranges instead of a negative number. (f5cba63)Range.inclusivenow returnsnullwhen the range's end is the smallest possible value, instead of a one-element range for an empty range. (f5cba63)DoubleRangeUntil.toString()now uses the exclusive..<endnotation. (f5cba63)UnboundedBound.operator ==no longer compares its type argument, which made equality asymmetric.const UnboundedBound()inside a generic class can't name that class's type parameter and becomes anUnboundedBound<Never>– which is what every range's unboundedstartBound/endBoundwas – soUnboundedBound<Never>() == UnboundedBound<num>()wasfalsewhile the reverse wastrue. An unbounded bound holds no value, so the type argument is phantom here. (1a1bc9e)AnyRangeno longer over-constrains the generic type of its bounds. (cdc895d)Iterable<RangeInclusive<C>>.unionno longer fails for an empty iterable. (ec94e1f)- The
IntRange*andDoubleRange*constructors now takeint/doubleparameters instead ofnum. (bae4053) Bound.laterStart(…)/Bound.earlierEnd(…)(thenBound.maxLower(…)/.minUpper(…)) andRangeInclusive.operator &now acceptnullarguments. (d2661fa)DerangedRangeOfStep.stepBy(…)'s return type is no longer nullable. (25461c2)
🏗️ Refactoring #
Progressionnow mixes inIterable<T>itself, soIntProgressionandStepProgressionno longer each declare it. (4561e4a)StepandStepUnlimitedmoved out ofprogression.dartinto their ownstep.dart, and the codecs intocodec.dart. (2456dad,d845a9d)
📜 Documentation updates #
Progression's documentation andtoString()no longer describe its end as exclusive. (4561e4a)- Swapped the mixed-up references in
Iterable<RangeInclusive>.unionand.intersection. (4561e4a) - Documented the
until/toconvention, and the discrete-vs-continuous reasoning behindinthaving one range type wheredoublehas two, in the README and onRangeBounds. (4561e4a,dbb1bd4) - Documented
Step,StepUnlimited, andprevious/next. (899db1c,4444618) - Added a serialization section to the README, documented
.iter, and shortened the rest. (de43393,d5f3d2c,41f9a95)
📦 Build & CI #
0.0.0 · 2025-01-07 #
Initial release 🎉