Skip to content

Commit d24dbb8

Browse files
authored
Documentation and API Completion (#23)
* README and home page * Option docs * Landing page for api docs * Result docs * Making Option/NumericOption/Result directly enumerable * Additional creation options * JSON converters don't need to be public * Generic math * Moving Async collection methods to the Async namespace * Stack/ImmutableStack * Queues * Sets * Tuple property names * Concurrent collections * More documentation * Incrementing version
1 parent 2e2b305 commit d24dbb8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1472
-331
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@ Option<int> ex3 = new Option<int>(42);
3333
Option<int> ex4 = Option<int>.None;
3434
Option<int> ex5 = Option.None<int>();
3535
Option<int> ex6 = default; // equivalent to Option.None<int>()
36+
Option<int> ex7 = 0.None(); // value is used only to determine type
3637
3738
int? maybeNull = GetPossiblyNullInteger();
38-
Option<int> ex7 = Option.Create(maybeNull); // null turns into Option.None
39+
Option<int> ex8 = Option.Create(maybeNull); // null turns into Option.None
3940
4041
// Or you can use 'using static' for more concise/F#/Rust-like syntax:
4142
using static RustyOptions.Option;
4243

43-
var ex8 = Some(42);
44-
var ex9 = None<int>();
44+
var ex9 = Some(42);
45+
var ex10 = None<int>();
4546
```
4647

4748
### Getting values from an Option

docs/api/index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
# PLACEHOLDER
2-
TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*!
1+
# RustyOptions
2+
3+
The RustyOptions library consists of two namespaces: `RustyOptions` and `RustyOptions.Async`.
4+
5+
You'll likely want to start with the static creation methods on [Option](./RustyOptions.Option.yml) or [Result](./RustyOptions.Result.yml).
6+
7+
For working with an instance, see the documention for [Option&lt;T&gt;](./RustyOptions.Option-1.yml) and [Result&lt;T, TErr&gt;](./RustyOptions.Result-2.yml).

docs/articles/async.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Async
2+
3+
The `RustyOptions.Async` namespace provides extension methods for `IAsyncEnumerable<T>`
4+
as well as async versions of RustyOptions API methods like `Map`, `OrElse`, `AndThen`.
5+
6+
## IAsyncEnumerable<T>
7+
8+
RustyOptions provides `FirstOrNoneAsync` for any async enumerable, as well as `Values` and `Errors` methods
9+
for async enumerables that return `Option<T>` or `Result<T, TErr>`.
10+
11+
## Async Option/Result Methods
12+
13+
RustyOptions provides asynchronous versions of most Option/Result API methods. There are 8 overloads of each
14+
method, for the various combinations of `Task` and `ValueTask` plus tasks that return an Option/Result vs
15+
Option/Result objects that return a task. Because there are so many overloads, they are found in the
16+
`RustyOptions.Async` namespace so that they don't clutter intellisense for non-async code.

docs/articles/collections.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Collections
2+
3+
RustyOptions provides extension methods that return options for collection operations that might not produce a value:
4+
- `TryXXX` methods have `XXXOrNone` equivalents.
5+
- `XXXOrDefault` methods have `XXXOrNone` equivalents.
6+
7+
In addition, the `.Values()` extension method will pull all the values that exist out of a collection of `Option<T>` or `Result<T, TErr>`,
8+
while the `.Errors()` extension method will pull all of the errors from a collection of `Result<T, TErr>`.
9+
10+
## Supported Collections
11+
- Any type implementing `IEnumerable<T>` (First, Last, Single, ElementAt)
12+
- `IDictionary<TKey, TValue>`, `IReadOnlyDictionary<TKey, TValue>`
13+
- `Stack<T>`, `ImmutableStack<T>`, `ConcurrentStack<T>`
14+
- `Queue<T>`, `PriorityQueue<T, TPriority>`, `ImmutableQueue<T>`, `ConcurrentQueue<T>`
15+
- `HashSet<T>`, `SortedSet<T>`, `ImmutableHashSet<T>`, `ImmutableSortedSet<T>`
16+
- `IProducerConsumerCollection<T>`, `ConcurrentBag<T>`
17+
18+
For details, see the [API Documentation](../api/RustyOptions.OptionCollectionExtensions.yml).

docs/articles/formatting.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Formatting Options and Results
2+
3+
RustyOptions types implement both `IFormattable` and `ISpanFormattable` for efficient use in string-building. Any format strings
4+
are passed through to the contained type.
5+
6+
```csharp
7+
Assert.Equal("Some(4,200.00)", Some(4200).ToString("n2", CultureInfo.InvariantCulture));
8+
Assert.Equal("None", None<int>().ToString("n2", CultureInfo.InvariantCulture));
9+
10+
Assert.Equal("Ok(4,200.00)", Ok(4200).ToString("n2", CultureInfo.InvariantCulture));
11+
Assert.Equal("Err(oops)", Err<int>("oops").ToString("n2", CultureInfo.InvariantCulture));
12+
```

docs/articles/fsharp.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# F# Compatibility
2+
3+
The F# language has its own built-in `Option`, `ValueOption`, and `Result` types, and both language features and
4+
utility functions to make these types ergonomic to work with in the context of F#.
5+
6+
There would be little value in extensive F# support for RustyOptions. Similarly, while the F# types can be used
7+
from C# by referencing the FSharp.Core namespace, they are not convenient to use from C#, and the FSharp.Core
8+
dependency is fairly heavy-weight for just these types.
9+
10+
RustyOptions is therefore designed to be used from C#. However, it provides the `RustyOptions.FSharp` nuget package,
11+
which provides convenient methods for converting between RustyOptions Option/Result types, and F# Option/Result types.
12+
If you are working with a mixture of F# and C# code, RustyOptions can ease the interop between these two languages:
13+
14+
- C# code receiving values from F# code can use `FromFSharpXXX` extension methods to convert F# Options or Results
15+
into RustyOptions equivalents that are easier to work with from C#.
16+
- C# code passing values to F# can use RustyOptions types for ease of working with in C#, then use `AsFSharpXXX` extension
17+
methods to convert to the equivalent F# types that the F# code expects.
18+
- F# code receiving values from C# that use RustyOptions types can use `Option.ofRustyOption` or `ValueOption.ofRustyOption` or
19+
`Result.ofRustyResult` to convert to types that are more convenient to use in F#.
20+
- F# code passing values to C# can use `Option.toRustyOption` or `ValueOption.toRustyOption` or `Result.toRustyResult` to
21+
convert to any RustyOptions types expected by C# code.
22+

docs/articles/intro.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/articles/json.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# JSON Serialization
2+
3+
RustyOptions types support serialization and deserialization using `System.Text.Json`.
4+
5+
**Options:**
6+
- A `Some` option will be serialized as the raw value in json.
7+
- A `None` option will be serialized as explicitly null in json.
8+
- When parsing, a missing property will be deserialized as `None`.
9+
- An explicitly null value will be deserialized as `None`.
10+
- Any other value will be deserialized as `Some`.
11+
12+
```csharp
13+
using RustyOptions;
14+
using System.Text.Json;
15+
16+
record Person(string First, Option<string> Middle, string Last, Option<int> Age);
17+
18+
string json1 = "{ \"first\": \"James\", \"middle\": \"Tiberius\", \"last\": \"Kirk\" }";
19+
string json2 = "{ \"first\": \"Martin\", \"last\": \"Redshirt\", \"age\": 23 }";
20+
21+
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
22+
23+
var person1 = JsonSerializer.Deserialize<Person>(json1, options);
24+
// [Person { First = James, Middle = Some(Tiberius), Last = Kirk, Age = None }]
25+
26+
var person1 = JsonSerializer.Deserialize<Person>(json2, options);
27+
// [Person { First = Martin, Middle = None, Last = Redshirt, Age = Some(23) }]
28+
```
29+
30+
**Results:**
31+
- A `Result<T, TErr>` will be serialized or parsed as an object that contains either an `ok` or an `err` property:
32+
- `{ "ok": 42 }`
33+
- `{ "err": "oops!" }`

docs/articles/math.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Generic Math
2+
3+
_NOTE: Generic Math is supported by .NET 7 and above only. This feature is not available in .NET 6._
4+
5+
.NET 7 introduces new [math-related generic interfaces](https://learn.microsoft.com/en-us/dotnet/standard/generics/math) to the base class library. RustyOptions provides the `NumericOption<T>` type to support this. `NumericOption` can store any struct that implements the `INumber<T>` interface, and allows you to transparently perform math operations on these optional values.
6+
7+
This works similarly to `Nullable<T>` which also [allows you to perform math operations on contained values](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types#lifted-operators). Such operations produce `null` if one or both operands are `null` in the case of `Nullable<T>`, and they produce `None` if one or both operands are `None` in the case of `NumericOption<T>`.
8+
9+
The difference is that this functionality in `Nullable<T>` depends on compiler support, which is not available to third-party types. This is why you can only do math with options in the .NET 7+ version of RustyOptions.
10+
11+
`NumericOption<T>` supports implicit conversion from `T` which means you can mix, for example, `NumericOption<int>` and `int` in the same expression, and it will just work.
12+
13+
## Example
14+
15+
```csharp
16+
using RustyOptions;
17+
using static RustyOptions.NumericOption;
18+
19+
var fifteen = Some(3) * 5;
20+
// returns Some(15)
21+
22+
var none = Some(3) * None<int>();
23+
// returns None typed to int
24+
25+
// You can even use Option<int> as the index value in a for loop!
26+
for (var i = Some(0); i < 5; i++)
27+
{
28+
// If you set i to None inside the loop,
29+
// the loop will exit when the current iteration completes,
30+
// as None is not less than 5.
31+
i = None<int>();
32+
}
33+
```
34+
35+
As you can see from the last example above, comparison operators such as `<` and increment operators like `++` also work with `NumericOption<T>`, even when comparing an option to a concrete number.
36+
37+
`NumericOption<T>` can be implicitly converted to and from `Option<T>` provided that the contained type is compatible with `NumericOption<T>`.
38+
39+
This support works for any built-in type that implements `INumber<T>`, any custom type that implements `INumber<T>`, and any future built-in types that implement `INumber<T>`. Enjoy!

docs/articles/option.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Options
2+
3+
The `Option` type is used when an actual value might not exist.
4+
It avoids the [billion-dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History) of null references
5+
by making it impossible to forget to check whether a possibly-missing value is present.
6+
7+
## Remarks
8+
9+
The following code illustrates a function which generates an option type.
10+
11+
```csharp
12+
using RustyOptions;
13+
14+
Option<int> KeepIfPositive(int x) => x > 0 ? Option.Some(x) : Option<int>.None;
15+
```
16+
17+
You can take advantage of the `using static` feature of C# for more concise syntax that is closer to
18+
that of languages with built-in Option types, such as F# or Rust.
19+
20+
```csharp
21+
using RustyOptions;
22+
using static RustyOptions.Option;
23+
24+
Option<int> KeepIfPositive(int x) => x > 0 ? Some(x) : None<int>();
25+
```
26+
27+
The value `None` is used when an option does not have the actual value.
28+
29+
## Using Options
30+
31+
Options are commonly used when a search does not return a matching result, as shown in the following code.
32+
33+
```csharp
34+
using RustyOptions;
35+
using static RustyOptions.Option;
36+
37+
Option<T> TryFindMatch<T>(IEnumerable<T> list, Func<T, bool> predicate)
38+
{
39+
foreach (var value in list)
40+
{
41+
if (predicate(value))
42+
{
43+
return Some(value);
44+
}
45+
}
46+
47+
return None<T>();
48+
}
49+
50+
// result1 is Some(100) and its type is Option<int>
51+
var result1 = TryFindMatch(new[] { 200, 100, 50, 25 }, x => x == 100);
52+
53+
// result2 is None and its type is Option<char>
54+
var result2 = TryFindMatch(new[] { 'a', 'b', 'c', 'd' }, x => x == 'y');
55+
```
56+
57+
In the previous code, the function `TryFindMatch` takes a list of values and a predicate function
58+
that returns a Boolean value. The function iterates the list, and if an element is found that satisfies
59+
the predicate, the iteration ends and the matching value is returned wrapped in a `Some` option. If
60+
the function reaches the end of the list without finding a match, `None` is returned.
61+
62+
RustyOptions provides extension methods for most collection types. For more information, see [Collections](collections.md).
63+
64+
Options can also be useful when a value might not exist, for example if it is possible that an exception will
65+
be thrown when you try to construct a value. The following code sample illustrates this.
66+
67+
```csharp
68+
using System.IO;
69+
using RustyOptions;
70+
using static RustyOptions.Option;
71+
72+
Option<FileStream> OpenFile(string filename)
73+
{
74+
try
75+
{
76+
var file = File.Open(filename, FileMode.Create);
77+
return Some(file);
78+
}
79+
catch (Exception ex)
80+
{
81+
Console.Error.WriteLine("An exception occurred opening file '{0}': {1}",
82+
filename, ex.Message);
83+
return None<FileStream>();
84+
}
85+
}
86+
```
87+
88+
The `OpenFile` function in the previous example returns a `FileStream` option if the file opens successfully,
89+
and `None` if an exception occurs. Depending on the situation, it may not be an appropriate design choice to
90+
catch an exception rather than allowing it to propagate.
91+
92+
## Null Values
93+
94+
Note that unlike the F# Option type, RustyOptions does not allow `Some(null)` - any attempt to create an option
95+
with a null value will result in `None`.
96+
97+
## Option Properties and Methods
98+
99+
The option type supports the [following properties and methods](../api/RustyOptions.Option-1.yml).
100+
101+
## Converting to Other Types
102+
103+
- Options can be converted to [Results](result.md) using the
104+
[OkOr](../api/RustyOptions.OptionResultExtensions.yml#RustyOptions_OptionResultExtensions_OkOr__2_RustyOptions_Option___0____1_)
105+
or [OkOrElse](../api/RustyOptions.OptionResultExtensions.yml#RustyOptions_OptionResultExtensions_OkOrElse__2_RustyOptions_Option___0__Func___1__) extension methods.
106+
107+
- Options can be converted to `IEnumerable<T>` using the [AsEnumerable](../api/RustyOptions.Option-1.yml#RustyOptions_Option_1_AsEnumerable) method.
108+
109+
- Options can be converted to `ReadOnlySpan<T>` using the [AsSpan](../api/RustyOptions.Option-1.yml#RustyOptions_Option_1_AsSpan) method.

0 commit comments

Comments
 (0)