-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathResult.cs
More file actions
202 lines (180 loc) · 6.06 KB
/
Copy pathResult.cs
File metadata and controls
202 lines (180 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
namespace RopStarterPack;
/// <summary>
/// Discriminated union: either Ok(value) or Err(error). Never both, never neither.
/// </summary>
public abstract record Result<T, E>
{
// Private constructor prevents external inheritance - only Ok and Err can exist
private Result() { }
// The two cases
public sealed record Ok(T Value) : Result<T, E>;
public sealed record Err(E Error) : Result<T, E>;
// Exhaustive pattern match - forces you to handle both cases
public TResult Match<TResult>(Func<T, TResult> ok, Func<E, TResult> err) =>
this switch
{
Ok(var value) => ok(value),
Err(var error) => err(error),
_ => throw new InvalidOperationException("Unreachable"),
};
// Transform success value (can't fail)
public Result<U, E> Map<U>(Func<T, U> f) =>
this switch
{
Ok(var value) => new Result<U, E>.Ok(f(value)),
Err(var error) => new Result<U, E>.Err(error),
_ => throw new InvalidOperationException("Unreachable"),
};
// Chain operations that can fail (aka FlatMap)
public Result<U, E> AndThen<U>(Func<T, Result<U, E>> f) =>
this switch
{
Ok(var value) => f(value),
Err(var error) => new Result<U, E>.Err(error),
_ => throw new InvalidOperationException("Unreachable"),
};
// Chain operations that can fail (async version)
public async Task<Result<U, E>> AndThen<U>(Func<T, Task<Result<U, E>>> f) =>
this switch
{
Ok(var value) => await f(value),
Err(var error) => new Result<U, E>.Err(error),
_ => throw new InvalidOperationException("Unreachable"),
};
// LINQ support
public Result<U, E> SelectMany<U>(Func<T, Result<U, E>> bind) => AndThen(bind);
public Result<V, E> SelectMany<U, V>(Func<T, Result<U, E>> bind, Func<T, U, V> project) =>
this switch
{
Ok(var t) => bind(t) switch
{
Result<U, E>.Ok(var u) => new Result<V, E>.Ok(project(t, u)),
Result<U, E>.Err(var e) => new Result<V, E>.Err(e),
_ => throw new InvalidOperationException("Unreachable"),
},
Err(var e) => new Result<V, E>.Err(e),
_ => throw new InvalidOperationException("Unreachable"),
};
}
/// <summary>
/// Static factory for creating Results.
/// </summary>
public static class Result
{
// Direct constructors - cleaner than new Result<T,E>.Ok/Err
public static Result<T, E> Ok<T, E>(T value) => new Result<T, E>.Ok(value);
public static Result<T, E> Err<T, E>(E error) => new Result<T, E>.Err(error);
// Sync - wrap throwing operations
public static Result<T, E> From<T, E>(Func<T> operation, Func<Exception, E> toError)
{
try
{
return new Result<T, E>.Ok(operation());
}
catch (Exception ex)
{
return new Result<T, E>.Err(toError(ex));
}
}
// Async (raw value)
public static async Task<Result<T, E>> From<T, E>(
Func<Task<T>> operation,
Func<Exception, E> toError
)
{
try
{
return new Result<T, E>.Ok(await operation());
}
catch (Exception ex)
{
return new Result<T, E>.Err(toError(ex));
}
}
// Async (Result)
public static async Task<Result<T, E>> From<T, E>(
Func<Task<Result<T, E>>> operation,
Func<Exception, E> toError
)
{
try
{
return await operation();
}
catch (Exception ex)
{
return new Result<T, E>.Err(toError(ex));
}
}
// Convert nullable to Result - Ok if value, Err if null
public static Result<T, E> FromNullable<T, E>(T? value, E errorIfNull)
where T : struct =>
value.HasValue ? new Result<T, E>.Ok(value.Value) : new Result<T, E>.Err(errorIfNull);
public static Result<T, E> FromNullable<T, E>(T? value, E errorIfNull)
where T : class =>
value is not null ? new Result<T, E>.Ok(value) : new Result<T, E>.Err(errorIfNull);
}
/// <summary>
/// Async extensions for Result.
/// </summary>
public static class ResultExtensions
{
// Async Match
public static async Task<TResult> Match<T, E, TResult>(
this Task<Result<T, E>> self,
Func<T, TResult> ok,
Func<E, TResult> err
)
{
var result = await self;
return result.Match(ok, err);
}
// Async Map (sync f)
public static async Task<Result<U, E>> Map<T, U, E>(this Task<Result<T, E>> self, Func<T, U> f)
{
var result = await self;
return result.Map(f);
}
// Async AndThen (sync f)
public static async Task<Result<U, E>> AndThen<T, U, E>(
this Task<Result<T, E>> self,
Func<T, Result<U, E>> f
)
{
var result = await self;
return result.AndThen(f);
}
// Async AndThen (async f)
public static async Task<Result<U, E>> AndThen<T, U, E>(
this Task<Result<T, E>> self,
Func<T, Task<Result<U, E>>> f
)
{
var result = await self;
return await result.AndThen(f);
}
// Async LINQ support
public static Task<Result<U, E>> SelectMany<T, U, E>(
this Task<Result<T, E>> self,
Func<T, Task<Result<U, E>>> f
) => self.AndThen(f);
public static async Task<Result<V, E>> SelectMany<T, U, V, E>(
this Task<Result<T, E>> self,
Func<T, Task<Result<U, E>>> f,
Func<T, U, V> project
)
{
var result = await self;
return result switch
{
Result<T, E>.Ok(var t) => await f(t) switch
{
Result<U, E>.Ok(var u) => new Result<V, E>.Ok(project(t, u)),
Result<U, E>.Err(var e) => new Result<V, E>.Err(e),
_ => throw new InvalidOperationException("Unreachable"),
},
Result<T, E>.Err(var e) => new Result<V, E>.Err(e),
_ => throw new InvalidOperationException("Unreachable"),
};
}
}