@@ -5,23 +5,181 @@ namespace Ametrin.Optional;
55
66public static class OptionLinqExtensions
77{
8+ // any already tries to get the non-enumerated count before calling the enumerator
89 public static Option < IEnumerable < T > > RejectEmpty < T > ( this IEnumerable < T > source )
910 => source is not null && source . Any ( ) ? Option . Success ( source ) : default ;
1011 public static Option < IEnumerable < T > > RejectEmpty < T > ( this Option < IEnumerable < T > > option )
11- => option . Require ( static collection => collection . Any ( ) ) ;
12+ => option . Require ( Enumerable . Any ) ;
1213 public static Result < IEnumerable < T > > RejectEmpty < T > ( this Result < IEnumerable < T > > option )
13- => option . RejectEmpty ( static value => new ArgumentException ( "Sequence was empty" ) ) ;
14+ => option . RejectEmpty ( static source => new ArgumentException ( "Sequence was empty" ) ) ;
1415 public static Result < IEnumerable < T > > RejectEmpty < T > ( this Result < IEnumerable < T > > option , Func < IEnumerable < T > , Exception > error )
15- => option . Require ( static collection => collection . Any ( ) , error ) ;
16+ => option . Require ( Enumerable . Any , error ) ;
17+ public static Result < IEnumerable < T > , E > RejectEmpty < T , E > ( this Result < IEnumerable < T > , E > option , E error )
18+ => option . Require ( Enumerable . Any , error ) ;
19+ public static Result < IEnumerable < T > , E > RejectEmpty < T , E > ( this Result < IEnumerable < T > , E > option , Func < IEnumerable < T > , E > error )
20+ => option . Require ( Enumerable . Any , error ) ;
1621
17- public static Option < T > FirstOrNone < T > ( this IEnumerable < T > source )
22+ public static Option < T > TryFirst < T > ( this IEnumerable < T > source )
1823 {
1924 using var enumerator = source . GetEnumerator ( ) ;
2025 return enumerator . MoveNext ( ) ? Option . Success ( enumerator . Current ) : default ;
2126 }
2227
2328 public static IEnumerable < T > WhereSuccess < T > ( this IEnumerable < Option < T > > source )
2429 => source . Where ( static option => option . _hasValue ) . Select ( static option => option . _value ) ;
30+ public static IEnumerable < T > WhereSuccess < T > ( this IEnumerable < Result < T > > source )
31+ => source . Where ( static option => option . _hasValue ) . Select ( static option => option . _value ) ;
32+ public static IEnumerable < T > WhereSuccess < T , E > ( this IEnumerable < Result < T , E > > source )
33+ => source . Where ( static option => option . _hasValue ) . Select ( static option => option . _value ) ;
34+
35+ public static IEnumerable < Exception > WhereError < T > ( this IEnumerable < Result < T > > source )
36+ => source . Where ( static option => ! option . _hasValue ) . Select ( static option => option . _error ) ;
37+ public static IEnumerable < E > WhereError < T , E > ( this IEnumerable < Result < T , E > > source )
38+ => source . Where ( static option => ! option . _hasValue ) . Select ( static option => option . _error ) ;
39+ public static IEnumerable < Exception > WhereError < T > ( this IEnumerable < ErrorState > source )
40+ => source . Where ( static option => option . _isError ) . Select ( static option => option . _error ) ;
41+ public static IEnumerable < E > WhereError < T , E > ( this IEnumerable < ErrorState < E > > source )
42+ => source . Where ( static option => option . _isError ) . Select ( static option => option . _error ) ;
43+
44+ public static Option < IReadOnlyList < T > > ValuesOrError < T > ( this IEnumerable < Option < T > > source )
45+ {
46+ var count = source . TryGetNonEnumeratedCount ( out var c ) ? c : - 1 ;
47+ if ( count is 0 ) return Option . Success < IReadOnlyList < T > > ( [ ] ) ;
48+ var values = CreateBag < T > ( count ) ;
49+
50+ foreach ( var result in source )
51+ {
52+ if ( result . Branch ( out var value ) )
53+ {
54+ values . Add ( value ) ;
55+ }
56+ else
57+ {
58+ values . Clear ( ) ;
59+ return default ;
60+ }
61+ }
62+
63+ return values ;
64+ }
65+
66+ public static Result < IReadOnlyList < T > > ValuesOrFirstError < T > ( this IEnumerable < Result < T > > source )
67+ {
68+ var count = source . TryGetNonEnumeratedCount ( out var c ) ? c : - 1 ;
69+ if ( count is 0 ) return Result . Success < IReadOnlyList < T > > ( [ ] ) ;
70+ var values = CreateBag < T > ( count ) ;
71+
72+ foreach ( var result in source )
73+ {
74+ if ( result . Branch ( out var value , out var error ) )
75+ {
76+ values . Add ( value ) ;
77+ }
78+ else
79+ {
80+ values . Clear ( ) ;
81+ return error ;
82+ }
83+ }
84+
85+ return values ;
86+ }
87+
88+ public static Result < IReadOnlyList < T > , E > ValuesOrFirstError < T , E > ( this IEnumerable < Result < T , E > > source )
89+ {
90+ var count = source . TryGetNonEnumeratedCount ( out var c ) ? c : - 1 ;
91+ if ( count is 0 ) return Result . Success < IReadOnlyList < T > , E > ( [ ] ) ;
92+ var values = CreateBag < T > ( count ) ;
93+
94+ foreach ( var result in source )
95+ {
96+ if ( result . Branch ( out var value , out var error ) )
97+ {
98+ values . Add ( value ) ;
99+ }
100+ else
101+ {
102+ values . Clear ( ) ;
103+ return error ;
104+ }
105+ }
106+
107+ return values ;
108+ }
109+
110+ public static void BranchInto < T > ( this IEnumerable < Result < T > > results , IList < T > values , IList < Exception > errors )
111+ {
112+ foreach ( var result in results )
113+ {
114+ if ( result . Branch ( out var value , out var error ) )
115+ {
116+ values . Add ( value ) ;
117+ }
118+ else
119+ {
120+ errors . Add ( error ) ;
121+ }
122+ }
123+ }
124+
125+ public static void BranchInto < T , E > ( this IEnumerable < Result < T , E > > results , IList < T > values , IList < E > errors )
126+ {
127+ foreach ( var result in results )
128+ {
129+ if ( result . Branch ( out var value , out var error ) )
130+ {
131+ values . Add ( value ) ;
132+ }
133+ else
134+ {
135+ errors . Add ( error ) ;
136+ }
137+ }
138+ }
139+
140+ public static ( IReadOnlyList < T > values , IReadOnlyList < Exception > errors ) Branch < T > ( this IEnumerable < Result < T > > results )
141+ {
142+ ArgumentNullException . ThrowIfNull ( results ) ;
143+
144+ var count = results . TryGetNonEnumeratedCount ( out var c ) ? c : - 1 ;
145+ if ( count is 0 ) return ( [ ] , [ ] ) ;
146+
147+ var ( values , errors ) = CreateBags < T , Exception > ( count ) ;
148+ results . BranchInto ( values , errors ) ;
149+
150+ return ( values , errors ) ;
151+ }
152+
153+ public static ( IReadOnlyList < T > values , IReadOnlyList < E > errors ) Branch < T , E > ( this IEnumerable < Result < T , E > > results )
154+ {
155+ ArgumentNullException . ThrowIfNull ( results ) ;
156+
157+ var count = results . TryGetNonEnumeratedCount ( out var c ) ? c : - 1 ;
158+ if ( count is 0 ) return ( [ ] , [ ] ) ;
159+
160+ var ( values , errors ) = CreateBags < T , E > ( count ) ;
161+ results . BranchInto ( values , errors ) ;
162+
163+ return ( values , errors ) ;
164+ }
165+
166+ private static ( List < T > values , List < E > errors ) CreateBags < T , E > ( int count , double expectedErrorRate = 0.01 )
167+ {
168+ // we assume most of the incoming values will be successes so we preallocate the full size
169+ var values = count > 0 ? new List < T > ( capacity : count ) : [ ] ;
170+ // in most cases only a fraction of values will be errors (this has not been benchmarked yet! first me must figure out what a common error rate is)
171+ var errors = count > 0 ? new List < E > ( capacity : ( int ) double . Round ( count * expectedErrorRate ) ) : [ ] ;
172+
173+ return ( values , errors ) ;
174+ }
175+
176+ private static List < T > CreateBag < T > ( int count )
177+ {
178+ // we assume most of the incoming values will be successes so we preallocate the full size
179+ return count > 0 ? new List < T > ( capacity : count ) : [ ] ;
180+ }
181+
182+ [ Obsolete ( "use Select(option => option.Map(map)). this method made things confusing" ) ]
25183 public static IEnumerable < Option < TResult > > Select < T , TResult > ( this IEnumerable < Option < T > > source , Func < T , TResult > map )
26184 => source . Select ( option => option . Map ( map ) ) ;
27185}
0 commit comments