From e8730102b29691e4015ae6a232cdc8a7f3583e32 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Sep 2023 23:04:53 +0800 Subject: [PATCH] Add string comparisons --- sugar/CHANGELOG.md | 4 +- sugar/lib/core.dart | 1 - sugar/lib/src/core/maybe.dart | 131 ---------------------------- sugar/lib/src/core/strings.dart | 77 ++++++++++++++++ sugar/test/src/core/maybe_test.dart | 45 ---------- 5 files changed, 79 insertions(+), 179 deletions(-) delete mode 100644 sugar/lib/src/core/maybe.dart delete mode 100644 sugar/test/src/core/maybe_test.dart diff --git a/sugar/CHANGELOG.md b/sugar/CHANGELOG.md index 47252e32..f4998501 100644 --- a/sugar/CHANGELOG.md +++ b/sugar/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.2.0 (Next) +## 4.0.0 (Next) ## `sugar.collection` - Add `Lists.toggleAll(...)` @@ -8,7 +8,7 @@ - Add `System.epochMilliseconds` - Add `System.epochMicroseconds` - **Breaking** - Change `Runtime` to `System` -- Deprecate `Maybe` extension for removal in Sugar 3.3.0. +- **Breaking** - Remove `Maybe` extension. - Remove `Iterables.indexed` since it already exists in Dart 3. ## `sugar.time` diff --git a/sugar/lib/core.dart b/sugar/lib/core.dart index 7bc83af4..a6e643bf 100644 --- a/sugar/lib/core.dart +++ b/sugar/lib/core.dart @@ -69,7 +69,6 @@ export 'src/core/comparables.dart'; export 'src/core/disposable.dart'; export 'src/core/equality.dart'; export 'src/core/functions.dart'; -export 'src/core/maybe.dart'; export 'src/core/result.dart'; export 'src/core/string_buffers.dart'; export 'src/core/strings.dart'; diff --git a/sugar/lib/src/core/maybe.dart b/sugar/lib/src/core/maybe.dart deleted file mode 100644 index 0b3e24e8..00000000 --- a/sugar/lib/src/core/maybe.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import 'package:sugar/core.dart'; - -/// A [Maybe] monad that may or may not contain a [T]. -/// -/// _All nullable types are `Maybe` monads_. Leveraging on the type system, this implementation is not an explicit container. -/// -/// Assuming: -/// ``` -/// Maybe(T) = Some(T) | None() -/// ``` -/// -/// It can be represented in the type system as: -/// ``` -/// T? = T | null -/// ``` -/// -/// Example: -/// ```dart -/// String foo(int? bar) { // int? is a Maybe(int) monad. -/// return bar.where((e) => e == 1).map((e) => e.toString())!; -/// } -/// ``` -/// -/// ## [Maybe] and collections -/// It is recommended to use an empty collection to represent `None()`. Thus, most of [Maybe]'s functions deliberately -/// do not work on collections. -/// -/// **Good:** -/// ```dart -/// List foo() { -/// if (notFound) return []; -/// } -/// ``` -/// -/// **Bad**: -/// ```dart -/// List? foo() { -/// if (notFound) return null; -/// } -/// ``` -/// -/// See: -/// * [FutureMaybe] for working with asynchronous `Maybe`s. -/// * [Result] for representing successes and failures. -@Deprecated('This extension was originally designed before the release of Dart 3 & pattern matching. It has been became clear that pattern matching is a superior alternative to this. It will be removed in Sugar 3.3.0') -extension Maybe on T? { - - /// Returns this if it is not null and satisfies [predicate]. Otherwise returns `null`. - /// - /// ```dart - /// String? foo = 'value'; - /// - /// foo.where((v) => v == 'value'); // 'value' - /// - /// foo.where((v) => v == 'other'); // null - /// - /// - /// String? bar = null; - /// - /// bar.where((e) => e == 'value'); // null - /// ``` - @useResult T? where(Predicate predicate) => this != null && predicate(this!) ? this : null; - - /// Returns [R?] produced by [function] if this is not null. Otherwise returns `null`. - /// - /// This function is similar to [map] except that it returns [R?] instead of [R]. - /// - /// ```dart - /// String? foo = 'value'; - /// - /// foo.bind((v) => 'other value'); // 'other value'; - /// - /// foo.bind((v) => null); // null; - /// - /// - /// String? bar = null; - /// - /// bar.bind((v) => 'other value'); // null - /// ``` - @useResult R? bind(R? Function(T value) function) => this == null ? null : function(this!); - - /// Returns [R] if this is not null. Otherwise returns null. - /// - /// This function is similar to [bind] except that it returns [R] instead of [R?] - /// - /// ```dart - /// String? foo = 'value'; - /// foo.map((v) => 'other value'); // 'other value' - /// - /// String? bar = null; - /// bar.map((v) => 'other value'); // null - /// ``` - @useResult R? map(R Function(T value) function) => this == null ? null : function(this!); - -} - -/// Provides functions for working with asynchronous `Maybe`s. -extension FutureMaybe on Future { - - /// Returns a [Future] if this `Future` does not complete as null. Otherwise returns a `Future` that completes as - /// null. - /// - /// Example: - /// ```dart - /// Future computeAsync(T value) async => 1; - /// - /// Future foo = Future.value('value'); - /// foo.pipe(computeAsync); // Future(1) - /// - /// Future bar = null; - /// bar.pipe(computeAsync); // Future(null) - /// ``` - /// - /// Chaining this function: - /// ```dart - /// Future addAsync(T value) async => value + 1; - /// - /// Future one = Future.value(1); - /// - /// one.pipe(addAsync).pipe(addAsync); // Future(3) - /// ``` - @useResult Future pipe(FutureOr Function(T value) function) async { - final value = await this; - return value == null ? null : function(value); - } - -} diff --git a/sugar/lib/src/core/strings.dart b/sugar/lib/src/core/strings.dart index f592a06c..7deba17c 100644 --- a/sugar/lib/src/core/strings.dart +++ b/sugar/lib/src/core/strings.dart @@ -408,4 +408,81 @@ extension Strings on String { /// See [isBlank]. @useResult bool get isNotBlank => trim().isNotEmpty; + + /// Returns whether this string is lexicographically less than [other]. + /// + /// If one string is a prefix of the other, then the shorter string is less than the longer string. This function does + /// not check for Unicode equivalence and is case sensitive. + /// + /// ```dart + /// 'A' < 'B'; // true + /// 'B < A'; // false + /// + /// 'A' < 'A'; // false + /// + /// 'A' < 'Apple'; // true + /// 'Apple' < 'A'; // false + /// + /// 'a' < 'A'; // true + /// 'A' < 'a'; // false + /// ``` + bool operator < (String other) => compareTo(other) < 0; + + /// Returns whether this string is lexicographically less than or equal to [other]. + /// + /// If one string is a prefix of the other, then the shorter string is less than the longer string. This function does + /// not check for Unicode equivalence and is case sensitive. + /// + /// ```dart + /// 'A' <= 'B'; // true + /// 'B <= A'; // false + /// + /// 'A' <= 'A'; // true + /// + /// 'A' <= 'Apple'; // true + /// 'Apple' <= 'A'; // false + /// + /// 'a' <= 'A'; // true + /// 'A' <= 'a'; // false + /// ``` + bool operator <= (String other) => compareTo(other) <= 0; + + /// Returns whether this string is lexicographically greater than [other]. + /// + /// If one string is a prefix of the other, then the larger string is greater than the shorter string. This function + /// does not check for Unicode equivalence and is case sensitive. + /// + /// ```dart + /// 'B > A'; // true + /// 'A' > 'B'; // false + /// + /// 'A' > 'A'; // false + /// + /// 'Apple' > 'A'; // true + /// 'A' > 'Apple'; // false + /// + /// 'A' > 'a'; // true + /// 'a' > 'A'; // false + /// ``` + bool operator > (String other) => compareTo(other) > 0; + + /// Returns whether this string is lexicographically greater than or equal to [other]. + /// + /// If one string is a prefix of the other, then the larger string is greater than the shorter string. This function + /// does not check for Unicode equivalence and is case sensitive. + /// + /// ```dart + /// 'B >= A'; // true + /// 'A' >= 'B'; // false + /// + /// 'A' >= 'A'; // true + /// + /// 'Apple' >= 'A'; // true + /// 'A' >= 'Apple'; // false + /// + /// 'A' >= 'a'; // true + /// 'a' >= 'A'; // false + /// ``` + bool operator >= (String other) => compareTo(other) >= 0; + } diff --git a/sugar/test/src/core/maybe_test.dart b/sugar/test/src/core/maybe_test.dart deleted file mode 100644 index 24449b0c..00000000 --- a/sugar/test/src/core/maybe_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:test/test.dart'; - -import 'package:sugar/core.dart'; - -void main() { - Future func(T value) async => 'value'; - - const int? nonnull = 1; // ignore: unnecessary_nullable_for_final_variable_declarations - const int? nullable = null; - - group('not null', () { - test('where predicate successful', () => expect(nonnull.where((value) => value == 1), 1)); - - test('where predicate not successful', () => expect(nonnull.where((value) => value == 2), null)); - - test('map', () => expect(nonnull.map((value) => value.toString()), '1')); - - test('bind not null', () => expect(nonnull.bind((value) => value.toString()), '1')); - - test('bind null', () => expect(nonnull.bind((value) => null), null)); - - test('bind async', () async => expect(await nonnull.bind(func), 'value')); - }); - - group('null', () { - test('where predicate successful', () => expect(nullable.where((value) => true), null)); - - test('where predicate not successful', () => expect(nullable.where((value) => false), null)); - - test('bind', () => expect(nullable.bind((value) => value.toString()), null)); - - test('map', () => expect(nullable.map((value) => value.toString()), null)); - - test('bind async', () async => expect(await nullable.bind(func), null)); - }); - - group('FutureMaybe', () { - Future compute(int value) async => value + 1; - - test('pipe not null', () async => expect(await compute(0).pipe(compute).pipe(compute), 3)); - - test('pipe null', () async => expect(await Future.value().pipe(compute).pipe(compute), null)); - }); - -}