Skip to content

Commit

Permalink
Merge pull request #14 from mcmah309/new_option
Browse files Browse the repository at this point in the history
3.0.0
  • Loading branch information
mcmah309 authored Dec 12, 2024
2 parents 5685cc1 + 5400029 commit 64156b2
Show file tree
Hide file tree
Showing 96 changed files with 4,818 additions and 2,314 deletions.
22 changes: 16 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 3.0.0

- Breaking: New sealed type `Option` implementation
- Breaking: Nullable methods added for every `Option` method and vice versa
- Breaking: Rename okay/o and e/error on Result to v
- Add `Fs` library
- Add `Env` library
- Misc additions
- Rename/refactor

## 2.0.2

- Add `Option.of`
Expand All @@ -8,14 +18,14 @@

## 2.0.0

- Breaking: Add `o`/`okay` to `Ok`, `e`/`error` to `Err`, `value` to `Option`
- Breaking: Change `channel` to `localChannel`
- Breaking: Remove deprecations
- Breaking: Remove individual library import files
- Breaking: Rename extensions
- Breaking: Rename errors
- Add `Path`, `UnixPath`, `WindowsPath`
- Add `KeyedMutex`
- Add `o`/`okay` to `Ok`, `e`/`error` to `Err`, `value` to `Option`
- Change `channel` to `localChannel`
- Remove deprecations
- Remove individual library import files
- Rename extensions
- Rename errors

## 1.3.7

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ See the [Documentation Book 📖](https://mcmah309.github.io/rust) for a deeper
---
> Goal: Get the index of every "!" in a string not followed by a "?"
**[Rust](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=6010cc86519e58e4592247403830cde7):**
**[Rust](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f8a2979808d21a7bfe22a3cfb70ec389):**
```rust
use std::iter::Peekable;

Expand All @@ -36,7 +36,7 @@ fn main() {
_ => continue,
}
}
assert_eq!(answer, [2, 7]);
println!("{:?}", answer); // [2, 7]
}
```
**Dart:**
Expand All @@ -59,11 +59,11 @@ void main() {
break;
case ["!", _]:
answer.push(index);
case [_, "!"] when iter.peek().isNone():
case [_, "!"] when iter.peek() == null: // or `iter.peekOpt().isNone()`
answer.push(index + 1);
}
}
expect(answer, [2, 7]);
print(answer); // [2, 7]
}
```

Expand Down
2 changes: 2 additions & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
- [array](./libs/array/array.md)
- [cell](./libs/cell/cell.md)
<!-- - [convert](./libs/convert/convert.md) -->
- [env](./libs/env/env.md)
- [fs](./libs/fs/fs.md)
- [iter](./libs/iter/iter.md)
- [ops](./libs/ops/ops.md)
- [option](./libs/option/option.md)
Expand Down
10 changes: 7 additions & 3 deletions book/src/introduction/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
From a language perspective we believe Dart is sadly lacking in a few areas, of which this package solves:

* Dart utilizes unchecked try/catch exceptions. Handling errors as values is preferred for maintainability, thus the `Result` type.
* Dart has nullable types but you cannot do null or non-null specific operations without a bunch of `if` statements. `Option<T>` fixes this with no runtime cost and you can easily switch back and forth to nullable types since it is just a zero cost extension type of `T?`.
* Dart is missing the functionality of Rust's `?` operator, so we implemented it in Dart.
* Dart has nullable types but you cannot do null or non-null specific operations without a bunch of `if` statements and
bugs may be introduced due to `T??` not being possible. `Option<T>` fixes this with no runtime cost and you can easily switch back and forth.
* Dart is missing the functionality of Rust's early return operator (`?`) operator, so we implemented it in Dart.
* Dart is missing a built in `Cell` type or equivalent (and `OnceCell`/`LazyCell`).
* Dart's `List` type is an array/vector union (it's growable or non-growable). This is not viewable at the type layer, which may lead to runtime exceptions and encourages using growable `List`s everywhere even when you do not need to, which is less performant. So we added `Arr` (array).
* Dart has no concept of a slice type, so allocating sub-lists is the only method, which is not that efficient. So we added `Slice<T>`.
* Dart's between isolate communication is by ports (`ReceivePort`/`SendPort`), which is untyped and horrible, we standardized this with introducing `channel` for typed bi-directional isolate communication.
* Dart's iteration methods are lacking for `Iterable` and `Iterator` (there are none! just `moveNext()` and `current`), while Rust has an abundance of useful methods. So we introduced Rust's `Iterator`.
* Dart's iteration methods are lacking for `Iterable` and `Iterator` (there are none! just `moveNext()` and `current`), while Rust has an abundance of useful methods. So we introduced Rust's `Iterator` as `Iter`.
* Dart does not have built in path manipulation. So, we added `Path`, a zero cost extension type of `String` for path manipulation.
* Dart's `File`/`Directory`/`Link` abstraction was the wrong choice and prone to exceptions. We believe `Fs` and `Path` are a stronger and safer alternative.
* No built in cross-platform environment introspect - `Platform` does not work on web. So we added `Env` which is cross-platform.

## I Know Rust, Can This Package Benefit My Team and I?
***
Expand Down
6 changes: 3 additions & 3 deletions book/src/libs/cell/cell.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ expect(result, const Ok(()));
result = cell.set(20);
expect(result, const Err(20));
```
The base type for all `OnceCell`s is `NullableOnceCell`.
The base type for all `OnceCell`s is `OnceCellNullable`.

## LazyCell
A value which is initialized on the first access.
Expand All @@ -68,7 +68,7 @@ int secondCall = lazyCell();
expect(callCount, equals(1));
expect(secondCall, equals(20));
```
The base type for all `LazyCell`s is `NullableLazyCell`.
The base type for all `LazyCell`s is `LazyCellNullable`.

## LazyCellAsync
A value which is asynchronously initialized on the first access.
Expand All @@ -86,4 +86,4 @@ int secondCall = lazyCell(); // Could also call `await lazyCell.force()` again.
expect(callCount, equals(1));
expect(secondCall, equals(20));
```
The base type for all `LazyCellAsync`s is `NullableLazyCellAsync`.
The base type for all `LazyCellAsync`s is `LazyCellNullableAsync`.
18 changes: 0 additions & 18 deletions book/src/libs/convert/convert.md
Original file line number Diff line number Diff line change
@@ -1,19 +1 @@
# Convert
***
## Infallible

`Infallible` is the error type for errors that can never happen. This can be useful for generic APIs that use Result
and parameterize the error type, to indicate that the result is always Ok. Thus these types expose `intoOk` and
`intoErr`.

```dart
Result<int, Infallible> x = Ok(1);
expect(x.intoOk(), 1);
Result<Infallible, int> w = Err(1);
expect(w.intoErr(), 1);
```

```
typedef Infallible = Never;
```
17 changes: 17 additions & 0 deletions book/src/libs/env/env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Env

Env introduces `Env` for handling the environment. It works like `Platform`,
except it is cross-platform (also works on web), since it is independent of `dart:io`, and has additional methods.

```dart
void main(){
if(Env.isWeb) {
print("On web, doing nothing.");
}
else if(Env.isLinux || Env.isMacOs) {
Env.currentDirectory = "/";
print("Moved current directory to root");
}
...
}
```
21 changes: 21 additions & 0 deletions book/src/libs/fs/fs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Fs

Fs introduces `Fs`, a container of static methods for working with the file system in a safe manner.
`Fs` combines many of the functionalities in `File`/`Directory`/`Link`/`FileStat`/`FileSystemEntity`
into one location and will never throw an exception. Instead of using instances of the previous
entities, `Fs` works only on paths.

```dart
Result<(), IoError> = await Fs.createDir("path/to/dir".asPath());
// handle
```
rather than
```dart
try {
await Directory("path/to/dir").create();
}
catch (e) {
// handle
}
// handle
```
14 changes: 11 additions & 3 deletions book/src/libs/iter/iter.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ makes working with collections of `rust` types and regular Dart types a breeze.
```dart
List<int> list = [1, 2, 3, 4, 5];
Iter<int> filtered = list.iter().filterMap((e) {
if (e % 2 == 0) {
return e * 2;
}
return null;
});
expect(filtered, [4, 8]);
// or
filtered = list.iter().filterMapOpt((e) {
if (e % 2 == 0) {
return Some(e * 2);
}
Expand All @@ -27,12 +35,12 @@ for (final e in iter.take(5).map((e) => e * e)) {
}
}
expect(collect, [4, 16]);
Option<int> next = iter.next();
expect(next, Some(6));
int? next = iter.next();
expect(next, 6);
collect.add(next.unwrap());
next = iter.next();
collect.add(next.unwrap());
expect(next, Some(7));
expect(next, 7);
while(iter.moveNext()){
collect.add(iter.current * iter.current);
}
Expand Down
4 changes: 2 additions & 2 deletions book/src/libs/ops/ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ They have two uses:
1. `RangeBounds` can be used to get a `Slice` of an `Arr`, `Slice`, or `List`.
```dart
void func(RangeBounds bounds) {
Arr<int> arr = Arr.range(0, 10);
Slice<int> slice = arr(bounds);
List<int> list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Slice<int> slice = list(bounds);
expect(slice, equals([4, 5, 6, 7, 8, 9]));
}
Expand Down
109 changes: 49 additions & 60 deletions book/src/libs/option/option.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# Option
***
Option represents the union of two types - `Some<T>` and `None`. An `Option<T>` is an extension type of `T?`. Therefore, `Option`
has zero runtime cost.

rust support nullable and `Option` implementations of classes and methods for ergonomic convenience where possible, but you
can easily switch between the two with no runtime cost.
`Option` represents the union of two types - `Some<T>` and `None`.

`Option` is easy to declare and translate back and forth between nullable types.
```dart
Option<int> option = None;
Option<int> option = Some(1);
int? nullable = option.v; // or `.value`
int? nullable = option.toNullable();
option = Option.of(nullable);
nullable = option as int?; // or
option = nullable as Option<int>; // or
```

### Usage
Expand All @@ -34,7 +29,7 @@ You can also use Option in pattern matching
switch(Some(2)){
case Some(:final v):
// do something
default:
case _:
// do something
}
```
Expand Down Expand Up @@ -66,65 +61,60 @@ FutureOption<double> earlyReturn() => Option.async(($) async {
...
});
```
### Discussion

### To Option or Not To Option
If Dart already supports nullable types, why use an option type? Nullable types may required an
uncomfortable level of null checking and nesting. Even so, one may also still need to write a null
assertion `!` for some edge cases where the compiler is not smart enough.
The `Option` type provides an alternative solution with methods and early return.
#### If Dart Has Nullable Types Why Ever Use `Option`?

Methods:
```dart
final profile;
final preferences;
switch (fetchUserProfile()
.map((e) => "${e.name} - profile")
.andThen((e) => Some(e).zip(fetchUserPreferences()))) {
case Some(:final value):
(profile, preferences) = value;
default:
return;
}
`Option` is wrapper around a value that may or may be set. A nullable type is a type that may or may not be set.
This small distinction leads to some useful differences:

print('Profile: $profile, Preferences: $preferences');
- Any extension method on `T?` also exists for `T`. So null specific extensions cannot be added.
Also since `T` is all types, there would be a lot of clashes with existing types if you tried to
do so - e.g. `map` on `Iterable`. While `Option` plays well for a pipeline style of programming.

```
Early Return Notation:
```dart
final (profile, preferences) = fetchUserProfile()
.map((e) => "${e.name} - profile")
.andThen((e) => Some(e).zip(fetchUserPreferences()))[$];
- `T??` is not possible, while Option<Option<T>> or Option<T?> is. This may be useful,
e.g.

print('Profile: $profile, Preferences: $preferences');
```
Traditional Null-Based Approach:
```dart
final profile = fetchUserProfile();
if (profile == null) {
return;
} else {
profile = profile.name + " - profile";
}
**No value at all** (`None`): The configuration value isn't defined at all.

final preferences = fetchUserPreferences();
if (preferences == null) {
return;
}
**A known absence of a value** (`Some(None)`): The configuration value is explicitly disabled.

print('Profile: $profile, Preferences: $preferences');
```
**A present value** (`Some(Some(value))`): The configuration value is explicitly set to value.

With nullable types, a separate field or enum/sealed class would be needed to keep track of this.

- Correctness of code and reducing bugs. As to why, e.g. consider `nth` which returns the nth index
of an iterable or null if the iterable does not have an nth index.
If the iterable is `Iterable<T?>`, then a null value from calling `nth` means the nth element is
either null or the iterable does not have n elements. While if `nth` rather returned `Option`,
if the nth index is null it returns `Some(null)` and if it does not have n elements it returns `None`.
One might accidentally mishandle the nullable case and assume the `nth` index does not actually exist,
when it is rather just null. While the second case with `Option` one is force to handle both cases.
This holds true for a lot of operations that might have unintended effects
e.g. `filterMap` - since null can be a valid state that should not be filtered.

These issues are not insurmountable, and in fact, most of the time nullable types are probably more concise
and easier to deal with. Therefore, for every method in this library that uses `T?` there is also an `Option`
version, usually suffixed with `..Opt`.

#### Drawbacks
Currently in Dart, one cannot rebind variables and `Option` does not support type promotion like nullable types.
> In some languages (like Rust, not Dart) `Option` can be passed around like a reference
> and values can be taken in and out of (transmutation). Thus visible to all with reference
> to the `Option`, unlike null. Implementing such an equivalence in Dart would remove pattern
> matching and const-ness.
#### Why Not To Use Option

- Null chaining operations with `?` is not possible with `Option`

- Currently in Dart, one cannot rebind variables and `Option` does not support type promotion like nullable types.
This makes using `Option` less ergonomic in some scenarios.
```dart
Option<int> xOpt = optionFunc();
int x;
switch(xOpt) {
Some(:final v):
case Some(:final v):
x = v;
default:
case _:
return;
}
// use `int` x
Expand All @@ -137,16 +127,15 @@ if(x == null){
}
// use `int` x
```
Fortunately, since `Option` is an extension type of `T?`. This can be overcome with no runtime cost.
Fortunately, it can be converted back and forth.
```dart
int? x = optionFunc().value;
int? x = optionFunc().toNullable();
if(x == null){
return;
}
// use `int` x
```

#### Conclusion
If you can't decide between the two, it is recommended to use the `Option` type as the return type, since it allows
early return, chaining operations, and easy conversion to a nullable type with `.v`/`.value`. But the choice is up to the developer.
You can easily use this package and never use `Option`.

The choice to use `Option` is up to the developer. You can easily use this package and never use `Option`.
5 changes: 5 additions & 0 deletions book/src/libs/panic/panic.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@ throw Unreachable();
unreachable();
```

### Handling Panic

For the most part, a panic is meant to abort your program. Thus one should only try to handle panics
sparingly, likely only at the root. Use `panicHandler` or `panicHandlerAsync` if this is desired.

[How to Never Unwrap Incorrectly]:https://github.com/mcmah309/rust/tree/master/lib/src/result#how-to-never-unwrap-incorrectly
Loading

0 comments on commit 64156b2

Please sign in to comment.