Skip to content

Commit bbc1bb6

Browse files
authored
Add Maybe back. (#72)
1 parent a5915ba commit bbc1bb6

File tree

12 files changed

+949
-0
lines changed

12 files changed

+949
-0
lines changed

Examples/Xunit/MaybeExamples.cs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
namespace Example.Tests;
2+
using Xunit;
3+
public class MaybeExamples
4+
{
5+
[Fact]
6+
public void Explicit_construction()
7+
{
8+
// Arrange
9+
Maybe<string> apple1 = Maybe.From("apple");
10+
var apple2 = Maybe.From("apple");
11+
12+
// Assert
13+
apple1.Should().BeOfType<Maybe<string>>();
14+
apple2.Should().BeOfType<Maybe<string>>();
15+
apple1.Value.Should().Be("apple");
16+
apple2.Value.Should().Be("apple");
17+
}
18+
19+
[Fact]
20+
public void Construction_None_X2F_No_Value()
21+
{
22+
// Arrange
23+
Maybe<string> fruit1 = Maybe.None<string>();
24+
Maybe<string> fruit2 = null; // reference type
25+
Maybe<int> fruit3 = default; // value type
26+
27+
// Assert
28+
fruit1.Should().BeOfType<Maybe<string>>();
29+
fruit1.HasNoValue.Should().BeTrue();
30+
31+
32+
fruit2.Should().BeOfType<Maybe<string>>();
33+
fruit2.HasNoValue.Should().BeTrue();
34+
35+
fruit3.Should().BeOfType<Maybe<int>>();
36+
fruit3.HasNoValue.Should().BeTrue();
37+
}
38+
39+
[Fact]
40+
public void Implicit_Conversion()
41+
{
42+
// Arrange
43+
Maybe<string> apple = "apple"; // implicit conversion
44+
45+
// Or as a method return value
46+
static Maybe<string> GetFruit(string fruit)
47+
{
48+
if (string.IsNullOrWhiteSpace(fruit))
49+
return Maybe.None<string>();
50+
51+
return fruit; // implicit conversion
52+
}
53+
54+
// Act
55+
var fruit = GetFruit("apple");
56+
57+
// Assert
58+
apple.Should().BeOfType<Maybe<string>>();
59+
apple.Value.Should().Be("apple");
60+
61+
fruit.Should().BeOfType<Maybe<string>>();
62+
fruit.Value.Should().Be("apple");
63+
64+
}
65+
66+
[Fact]
67+
public void Equality()
68+
{
69+
// Arrange
70+
Maybe<string> apple = "apple";
71+
Maybe<string> orange = "orange";
72+
string alsoOrange = "orange";
73+
Maybe<string> noFruit = Maybe.None<string>();
74+
75+
// Act
76+
77+
78+
// Assert
79+
apple.Should().NotBe(orange);
80+
orange.Should().Be(alsoOrange);
81+
noFruit.Should().NotBe(orange);
82+
}
83+
84+
85+
[Fact]
86+
public void Convert_to_string()
87+
{
88+
// Arrange
89+
Maybe<string> apple = "apple";
90+
Maybe<string> noFruit = Maybe.None<string>();
91+
92+
// Act
93+
94+
95+
// Assert
96+
apple.ToString().Should().Be("apple");
97+
noFruit.ToString().Should().Be("Maybe has no value.");
98+
}
99+
100+
[Fact]
101+
public void GetValueOrThrow()
102+
{
103+
// Arrange
104+
Maybe<string> apple = "apple";
105+
Maybe<string> noFruit = Maybe.None<string>();
106+
107+
// Act
108+
var action1 = () => apple.GetValueOrThrow();
109+
var action2 = () => noFruit.GetValueOrThrow();
110+
111+
// Assert
112+
action1.Should().NotThrow<InvalidOperationException>();
113+
action2.Should().Throw<InvalidOperationException>();
114+
}
115+
116+
[Fact]
117+
public void HasValue_and_HasNoValue()
118+
{
119+
// Arrange
120+
Maybe<string> apple = "apple";
121+
Maybe<string> noFruit = Maybe.None<string>();
122+
123+
// Act
124+
125+
126+
// Assert
127+
apple.HasValue.Should().BeTrue();
128+
noFruit.HasNoValue.Should().BeTrue();
129+
}
130+
131+
[Fact]
132+
public void GetValueOrDefault()
133+
{
134+
// Arrange
135+
Maybe<string> apple = "apple";
136+
Maybe<string> unknownFruit = Maybe.None<string>();
137+
138+
// Act
139+
string appleValue = apple.GetValueOrDefault("banana");
140+
string unknownFruitValue = unknownFruit.GetValueOrDefault("banana");
141+
142+
// Assert
143+
apple.Should().Be("apple");
144+
unknownFruitValue.Should().Be("banana");
145+
}
146+
}
147+

RailwayOrientedProgramming/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ public readonly struct Result<TValue>
2121
public static implicit operator Result<TValue>(Error error) => Result.Failure<TValue>(error);
2222
}
2323
```
24+
25+
### [Maybe](RailwayOrientedProgramming\src\Maybe\Maybe{T}.cs)
26+
27+
Maybe object holds a value or nothing. It is defined as
28+
29+
```csharp
30+
public readonly struct Maybe<T> :
31+
IEquatable<T>,
32+
IEquatable<Maybe<T>>
33+
where T : notnull
34+
{
35+
public T Value;
36+
public bool HasValue;
37+
public bool HasNoValue;
38+
}
39+
```
2440

2541
### Functions
2642

@@ -59,3 +75,12 @@ Tee calls the given function if the result is in success state and returns the s
5975
### Finally
6076

6177
Finally unwraps the `Result` and returns the success value or the error.
78+
79+
### Maybe
80+
81+
Maybe states if it contains a value or not.
82+
It has the following methods:
83+
84+
- HasValue - returns true if it has a value.
85+
- HasNoValue - returns true if it does not have a value.
86+
- Value - returns the value if it has a value. Otherwise `InvalidOperationException`
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace FunctionalDDD;
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
public static partial class MaybeExtensions
6+
{
7+
/// <summary>
8+
/// Converts the <see cref="Nullable"/> struct to a <see cref="Maybe{T}"/>.
9+
/// </summary>
10+
/// <returns>Returns the <see cref="Maybe{T}"/> equivalent to the <see cref="Nullable{T}"/>.</returns>
11+
public static Maybe<T> AsMaybe<T>(in this T? value) where T : struct =>
12+
value is null ? default : new(value.Value);
13+
14+
/// <summary>
15+
/// Wraps the class instance in a <see cref="Maybe{T}"/>.
16+
/// </summary>
17+
/// <returns>Returns <see cref="Maybe.None"/> if the class instance is null, otherwise returns <see cref="Maybe.From{T}(T)"/>.</returns>
18+
public static Maybe<T> AsMaybe<T>([MaybeNull] this T value) where T : class =>
19+
value is null ? default : new(value);
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+

2+
namespace FunctionalDDD;
3+
4+
public static partial class MaybeExtensions
5+
{
6+
/// <summary>
7+
/// Converts the <see cref="Maybe{T}"/> to a <see cref="Nullable"/> struct.
8+
/// </summary>
9+
/// <returns>Returns the <see cref="Nullable{T}"/> equivalent to the <see cref="Maybe{T}"/>.</returns>
10+
public static T? AsNullable<T>(in this Maybe<T> value)
11+
where T : struct
12+
{
13+
if (value.TryGetValue(out var result))
14+
{
15+
return result;
16+
}
17+
return default;
18+
}
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace FunctionalDDD;
2+
3+
public static partial class MaybeExtensions
4+
{
5+
public static Result<TOk> ToResult<TOk>(in this Maybe<TOk> maybe, Error error)
6+
where TOk : notnull
7+
{
8+
if (maybe.HasNoValue)
9+
return Result.Failure<TOk>(error);
10+
11+
return Result.Success<TOk>(maybe.GetValueOrThrow());
12+
}
13+
14+
public static async Task<Result<TOk>> ToResultAsync<TOk>(this Task<Maybe<TOk>> maybeTask, Error errors)
15+
where TOk : notnull
16+
{
17+
var maybe = await maybeTask.ConfigureAwait(false);
18+
return maybe.ToResult(errors);
19+
}
20+
21+
public static async ValueTask<Result<TOk>> ToResultAsync<TOk>(this ValueTask<Maybe<TOk>> maybeTask, Error errors)
22+
where TOk : notnull
23+
{
24+
Maybe<TOk> maybe = await maybeTask;
25+
return maybe.ToResult(errors);
26+
}
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace FunctionalDDD;
2+
3+
public sealed class Maybe
4+
{
5+
private Maybe()
6+
{
7+
}
8+
9+
public static Maybe<T> None<T>() where T : notnull => new();
10+
11+
public static Maybe<T> From<T>(T? value) where T : notnull => new(value);
12+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
namespace FunctionalDDD;
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
public readonly struct Maybe<T> :
6+
IEquatable<T>,
7+
IEquatable<Maybe<T>>
8+
where T : notnull
9+
{
10+
private readonly bool _isValueSet;
11+
private readonly T? _value;
12+
13+
private const string NoValue = "Maybe has no value.";
14+
15+
public T GetValueOrThrow(string? errorMessage = null)
16+
{
17+
if (_isValueSet)
18+
return _value!;
19+
20+
throw new InvalidOperationException(errorMessage ?? NoValue);
21+
}
22+
23+
public T GetValueOrDefault(T defaultValue)
24+
{
25+
if (_isValueSet)
26+
return _value!;
27+
28+
return defaultValue;
29+
}
30+
31+
public bool TryGetValue([MaybeNullWhen(false)] out T value)
32+
{
33+
value = _value;
34+
return _isValueSet;
35+
}
36+
37+
public T Value => GetValueOrThrow();
38+
39+
public bool HasValue => _isValueSet;
40+
41+
public bool HasNoValue => !_isValueSet;
42+
43+
internal Maybe(T? value)
44+
{
45+
_isValueSet = value is not null;
46+
_value = value!;
47+
}
48+
49+
public static implicit operator Maybe<T>(T? value) =>
50+
value is Maybe<T> maybe ? maybe : new(value);
51+
52+
public static bool operator ==(Maybe<T> maybe, T value) => maybe.Equals(value);
53+
54+
public static bool operator !=(Maybe<T> maybe, T value) => !maybe.Equals(value);
55+
56+
public static bool operator ==(Maybe<T> maybe, object other) => maybe.Equals(other);
57+
58+
public static bool operator !=(Maybe<T> maybe, object other) => !maybe.Equals(other);
59+
60+
public static bool operator ==(Maybe<T> first, Maybe<T> second) => first.Equals(second);
61+
62+
public static bool operator !=(Maybe<T> first, Maybe<T> second) => !first.Equals(second);
63+
64+
public override bool Equals(object? obj) =>
65+
obj switch
66+
{
67+
Maybe<T> other => Equals(other),
68+
T other => Equals(other),
69+
_ => false,
70+
};
71+
72+
public bool Equals(Maybe<T> other) =>
73+
_isValueSet && other._isValueSet
74+
? EqualityComparer<T>.Default.Equals(_value, other._value)
75+
: !_isValueSet && !other._isValueSet;
76+
77+
public bool Equals(T? other) =>
78+
_isValueSet
79+
? EqualityComparer<T>.Default.Equals(_value, other)
80+
: !_isValueSet;
81+
82+
public override int GetHashCode() => _value?.GetHashCode() ?? 0;
83+
84+
public override string ToString() => _value?.ToString() ?? NoValue;
85+
}

0 commit comments

Comments
 (0)