Skip to content

Commit e5bb3d1

Browse files
authored
SelectWhere support (#24)
* Clean-up * SelectWhere * Bumping version number
1 parent 823d93d commit e5bb3d1

File tree

11 files changed

+98
-52
lines changed

11 files changed

+98
-52
lines changed

.github/workflows/build-and-test.yml

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ name: CI
33
on:
44
push:
55
pull_request:
6-
branches: [ main ]
6+
branches: [main]
77
paths:
8-
- '**.cs'
9-
- '**.csproj'
8+
- "**.cs"
9+
- "**.csproj"
1010

1111
env:
12-
DOTNET_VERSION: '7.0.200' # The .NET SDK version to use
12+
DOTNET_VERSION: "7.0.203" # The .NET SDK version to use
1313

1414
jobs:
1515
build-and-test:
@@ -23,21 +23,21 @@ jobs:
2323
working-directory: src
2424

2525
steps:
26-
- uses: actions/checkout@v3
27-
- name: Setup .NET
28-
uses: actions/setup-dotnet@v3
29-
with:
30-
dotnet-version: ${{ env.DOTNET_VERSION }}
31-
32-
- name: Install dependencies
33-
run: dotnet restore
34-
35-
- name: Build
36-
run: dotnet build --no-restore --configuration Release
37-
38-
- name: Test
39-
run: dotnet test --no-restore --verbosity normal -p:CollectCoverage=true -p:CoverletOutput=TestResults/ -p:CoverletOutputFormat=opencover
40-
41-
- name: Upload coverage reports to Codecov
42-
uses: codecov/codecov-action@v3
43-
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'pull_request' }}
26+
- uses: actions/checkout@v3
27+
- name: Setup .NET
28+
uses: actions/setup-dotnet@v3
29+
with:
30+
dotnet-version: ${{ env.DOTNET_VERSION }}
31+
32+
- name: Install dependencies
33+
run: dotnet restore
34+
35+
- name: Build
36+
run: dotnet build --no-restore --configuration Release
37+
38+
- name: Test
39+
run: dotnet test --no-restore --verbosity normal -p:CollectCoverage=true -p:CoverletOutput=TestResults/ -p:CoverletOutputFormat=opencover
40+
41+
- name: Upload coverage reports to Codecov
42+
uses: codecov/codecov-action@v3
43+
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'pull_request' }}

src/RustyOptions.FSharp/RustyOptions.FSharp.fsproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
55
<GenerateDocumentationFile>true</GenerateDocumentationFile>
66
<AssemblyName>RustyOptions.FSharp</AssemblyName>
7-
<Version>0.8.0</Version>
8-
<ReleaseVersion>0.8.0</ReleaseVersion>
7+
<Version>0.9.0</Version>
8+
<ReleaseVersion>0.9.0</ReleaseVersion>
99
<PackageId>RustyOptions.FSharp</PackageId>
1010
<Authors>Joel Mueller</Authors>
1111
<Company></Company>

src/RustyOptions.Tests/NumericOptionMathTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
using System.Globalization;
44
using System.Numerics;
5-
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel;
65
using static RustyOptions.NumericOption;
76

87
namespace RustyOptions.Tests;

src/RustyOptions.Tests/OptionCollectionTests.cs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ public void CanGetFirst()
106106
[Fact]
107107
public void CanGetFirstMatch()
108108
{
109-
static bool Predicate(int x) => (x & 1) == 0;
109+
static bool IsEven(int x) => (x & 1) == 0;
110110

111111
var empty = Array.Empty<int>();
112112
var notEmpty = new[] { 3, 5, 6, 7, 8, 9 };
113113
var noMatches = new[] { 1, 3, 5, 7 };
114114

115-
Assert.Equal(None<int>(), empty.FirstOrNone(Predicate));
116-
Assert.Equal(Some(6), notEmpty.FirstOrNone(Predicate));
117-
Assert.Equal(None<int>(), noMatches.FirstOrNone(Predicate));
115+
Assert.Equal(None<int>(), empty.FirstOrNone(IsEven));
116+
Assert.Equal(Some(6), notEmpty.FirstOrNone(IsEven));
117+
Assert.Equal(None<int>(), noMatches.FirstOrNone(IsEven));
118118
}
119119

120120
[Fact]
@@ -140,7 +140,7 @@ public void CanGetLast()
140140
[Fact]
141141
public void CanGetLastMatch()
142142
{
143-
static bool Predicate(int x) => (x & 1) == 0;
143+
static bool IsEven(int x) => (x & 1) == 0;
144144

145145
IList<int> empty = Array.Empty<int>();
146146
IReadOnlyList<int> emptyReadOnly = new ReadOnlyList<int>(empty);
@@ -154,17 +154,17 @@ public void CanGetLastMatch()
154154
IReadOnlyList<int> noMatchesReadOnly = new ReadOnlyList<int>(noMatches);
155155
IEnumerable<int> noMatchesEnumerable = Enumerate(noMatches);
156156

157-
Assert.Equal(None<int>(), empty.LastOrNone(Predicate));
158-
Assert.Equal(None<int>(), emptyReadOnly.LastOrNone(Predicate));
159-
Assert.Equal(None<int>(), emptyEnumerable.LastOrNone(Predicate));
157+
Assert.Equal(None<int>(), empty.LastOrNone(IsEven));
158+
Assert.Equal(None<int>(), emptyReadOnly.LastOrNone(IsEven));
159+
Assert.Equal(None<int>(), emptyEnumerable.LastOrNone(IsEven));
160160

161-
Assert.Equal(Some(8), notEmpty.LastOrNone(Predicate));
162-
Assert.Equal(Some(8), notEmptyReadOnly.LastOrNone(Predicate));
163-
Assert.Equal(Some(8), notEmptyEnumerable.LastOrNone(Predicate));
161+
Assert.Equal(Some(8), notEmpty.LastOrNone(IsEven));
162+
Assert.Equal(Some(8), notEmptyReadOnly.LastOrNone(IsEven));
163+
Assert.Equal(Some(8), notEmptyEnumerable.LastOrNone(IsEven));
164164

165-
Assert.Equal(None<int>(), noMatches.LastOrNone(Predicate));
166-
Assert.Equal(None<int>(), noMatchesReadOnly.LastOrNone(Predicate));
167-
Assert.Equal(None<int>(), noMatchesEnumerable.LastOrNone(Predicate));
165+
Assert.Equal(None<int>(), noMatches.LastOrNone(IsEven));
166+
Assert.Equal(None<int>(), noMatchesReadOnly.LastOrNone(IsEven));
167+
Assert.Equal(None<int>(), noMatchesEnumerable.LastOrNone(IsEven));
168168
}
169169

170170
[Fact]
@@ -198,7 +198,7 @@ public void CanGetSingle()
198198
[Fact]
199199
public void CanGetSingleMatch()
200200
{
201-
static bool Predicate(int x) => (x & 1) == 0;
201+
static bool IsEven(int x) => (x & 1) == 0;
202202

203203
var empty = Array.Empty<int>();
204204
var singleWithMatch = new[] { 4 };
@@ -207,12 +207,12 @@ public void CanGetSingleMatch()
207207
var manyNoMatch = new[] { 3, 5 };
208208
var manyWithManyMatches = new[] { 2, 3, 4, 5, 6 };
209209

210-
Assert.Equal(None<int>(), empty.SingleOrNone(Predicate));
211-
Assert.Equal(Some(4), singleWithMatch.SingleOrNone(Predicate));
212-
Assert.Equal(None<int>(), singleNoMatch.SingleOrNone(Predicate));
213-
Assert.Equal(Some(4), manyWithMatch.SingleOrNone(Predicate));
214-
Assert.Equal(None<int>(), manyNoMatch.SingleOrNone(Predicate));
215-
Assert.Equal(None<int>(), manyWithManyMatches.SingleOrNone(Predicate));
210+
Assert.Equal(None<int>(), empty.SingleOrNone(IsEven));
211+
Assert.Equal(Some(4), singleWithMatch.SingleOrNone(IsEven));
212+
Assert.Equal(None<int>(), singleNoMatch.SingleOrNone(IsEven));
213+
Assert.Equal(Some(4), manyWithMatch.SingleOrNone(IsEven));
214+
Assert.Equal(None<int>(), manyNoMatch.SingleOrNone(IsEven));
215+
Assert.Equal(None<int>(), manyWithManyMatches.SingleOrNone(IsEven));
216216
}
217217

218218
[Fact]
@@ -245,6 +245,20 @@ public void CanGetElementAt()
245245
Assert.Equal(Some(5), notEmptyEnumerable.ElementAtOrNone(5));
246246
}
247247

248+
[Fact]
249+
public void CanSelectWhere()
250+
{
251+
var strings = new[] { "1", "two", "NaN", "four", "5", "six", "7", "eight", "9", "10" };
252+
253+
#if NET7_0_OR_GREATER
254+
var ints = strings.SelectWhere(Option.Parse<int>);
255+
#else
256+
var ints = strings.SelectWhere(Option.Bind<string, int>(int.TryParse));
257+
#endif
258+
259+
Assert.Equal(new[] { 1, 5, 7, 9, 10 }, ints);
260+
}
261+
248262
[Fact]
249263
public void CanPeekStack()
250264
{

src/RustyOptions.Tests/RustyOptions.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<LangVersion>11</LangVersion>
99
<IsPackable>false</IsPackable>
1010
<AssemblyName>RustyOptions.Tests</AssemblyName>
11-
<ReleaseVersion>0.8.0</ReleaseVersion>
11+
<ReleaseVersion>0.9.0</ReleaseVersion>
1212
</PropertyGroup>
1313

1414
<ItemGroup>

src/RustyOptions.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ Global
4747
EndGlobalSection
4848
GlobalSection(MonoDevelopProperties) = preSolution
4949
description = Option and Result types for C#, inspired by Rust
50-
version = 0.8.0
50+
version = 0.9.0
5151
EndGlobalSection
5252
EndGlobal

src/RustyOptions/NumericOption{T}.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if NET7_0_OR_GREATER
22

3+
using System.ComponentModel;
34
using System.Diagnostics.CodeAnalysis;
45
using System.Globalization;
56
using System.Numerics;
@@ -164,6 +165,7 @@ public IEnumerable<T> AsEnumerable()
164165
/// Returns an enumerator for the option.
165166
/// </summary>
166167
/// <returns>The enumerator.</returns>
168+
[EditorBrowsable(EditorBrowsableState.Advanced)]
167169
public IEnumerator<T> GetEnumerator()
168170
{
169171
if (_isSome)

src/RustyOptions/OptionCollectionExtensions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,33 @@ public static Option<T> ElementAtOrNone<T>(this IEnumerable<T> self, int index)
346346
return default;
347347
}
348348

349+
/// <summary>
350+
/// Applies a function to each element of a sequence and returns a sequence of the values inside any <c>Some</c> results.
351+
/// Can be used to transform and filter in a single operation, similar to applying <c>Select/Where/Select</c>,
352+
/// but with better performance.
353+
/// </summary>
354+
/// <typeparam name="T1">The type contained in the incoming sequence.</typeparam>
355+
/// <typeparam name="T2">The type contained in the resulting sequence.</typeparam>
356+
/// <param name="self">The incoming sequence.</param>
357+
/// <param name="selector">A function that takes a value from the in coming sequence, and returns an <see cref="Option{T}"/> that will have any value included in the resulting sequence.</param>
358+
/// <returns>A sequence containing the values for which the <paramref name="selector"/> function returns <c>Some</c>.</returns>
359+
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="self"/> or <paramref name="selector"/> is null.</exception>
360+
public static IEnumerable<T2> SelectWhere<T1, T2>(this IEnumerable<T1> self, Func<T1, Option<T2>> selector)
361+
where T1 : notnull
362+
where T2 : notnull
363+
{
364+
ThrowIfNull(self);
365+
ThrowIfNull(selector);
366+
367+
foreach (var item in self)
368+
{
369+
if (selector(item).IsSome(out var value))
370+
{
371+
yield return value;
372+
}
373+
}
374+
}
375+
349376
/// <summary>
350377
/// Returns an <see cref="Option{T}"/> that contains the object at the top of the <c>Stack</c> if one is present.
351378
/// The object is not removed from the <c>Stack</c>.

src/RustyOptions/Option{T}.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.ComponentModel;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
45
using System.Text.Json.Serialization;
@@ -137,6 +138,7 @@ public IEnumerable<T> AsEnumerable()
137138
/// Returns an enumerator for the option.
138139
/// </summary>
139140
/// <returns>The enumerator.</returns>
141+
[EditorBrowsable(EditorBrowsableState.Advanced)]
140142
public IEnumerator<T> GetEnumerator()
141143
{
142144
if (_isSome)

src/RustyOptions/Result{T,TErr}.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.ComponentModel;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
45
using System.Text.Json.Serialization;
@@ -236,6 +237,7 @@ public IEnumerable<T> AsEnumerable()
236237
/// Returns an enumerator for the option.
237238
/// </summary>
238239
/// <returns>The enumerator.</returns>
240+
[EditorBrowsable(EditorBrowsableState.Advanced)]
239241
public IEnumerator<T> GetEnumerator()
240242
{
241243
if (_isOk)

0 commit comments

Comments
 (0)