Skip to content

Add or else methods to option closes #31#82

Merged
ax0l0tl merged 1 commit into
mainfrom
feature/or-else2
Mar 10, 2026
Merged

Add or else methods to option closes #31#82
ax0l0tl merged 1 commit into
mainfrom
feature/or-else2

Conversation

@ax0l0tl
Copy link
Copy Markdown
Member

@ax0l0tl ax0l0tl commented Mar 10, 2026

No description provided.

@ax0l0tl ax0l0tl requested a review from Copilot March 10, 2026 08:42
@ax0l0tl ax0l0tl merged commit afd91cb into main Mar 10, 2026
5 of 6 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OrElse fallbacks to Option<T> (and Task<Option<T>>) to support providing alternate values/options when the current option is None, closing #31.

Changes:

  • Add Option<T>.OrElse(...) overloads for eager and lazy fallbacks, including async fallbacks.
  • Add Task<Option<T>>.OrElse(...) extension overloads for chaining asynchronous options.
  • Add a new test case covering basic OrElse behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
Source/FunicularSwitch/Option.cs Introduces OrElse APIs on Option<T> and Task<Option<T>>, plus minor refactors/formatting changes.
Source/Tests/FunicularSwitch.Test/OptionSpecs.cs Adds a new OrElse unit test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ShouldBeSome42(some.OrElse(2));

ShouldBeSome42(await none.OrElse(() => Task.FromResult(Some(42))));
return;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return; is unnecessary here and makes the local function placement confusing. Consider removing the early return and just keep the local helper at the end of the method (or move the helper above the assertions).

Suggested change
return;

Copilot uses AI. Check for mistakes.

public Option<T> OrElse(Option<T> other) => _isSome ? this : other;
public Option<T> OrElse(Func<Option<T>> other) => _isSome ? this : other();
public Task<Option<T>> OrElse(Task<Option<T>> other) => OrElse(() => other);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrElse(Task<Option<T>> other) can leave other running without being awaited when the current option is Some. If other faults, the exception can become unobserved (and may surface via TaskScheduler.UnobservedTaskException). Consider removing this overload in favor of OrElse(Func<Task<Option<T>>>), or ensure exceptions from other are observed even when it isn't used (e.g., via a continuation).

Suggested change
public Task<Option<T>> OrElse(Task<Option<T>> other) => OrElse(() => other);
public Task<Option<T>> OrElse(Task<Option<T>> other)
{
other.ContinueWith(
t =>
{
var _ = t.Exception;
},
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
return OrElse(() => other);
}

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +228
public static async Task<Option<T>> OrElse<T>(this Task<Option<T>> option, Task<Option<T>> other)
{
var result = await option.ConfigureAwait(false);
return await result.OrElse(other).ConfigureAwait(false);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Task<Option<T>> extension overload OrElse(..., Task<Option<T>> other) has the same unobserved-exception risk as the instance overload: if the awaited option is Some, other is never awaited, so faults may go unobserved. Prefer a Func<Task<Option<T>>> overload only, or observe other's exceptions when it isn't needed.

Suggested change
public static async Task<Option<T>> OrElse<T>(this Task<Option<T>> option, Task<Option<T>> other)
{
var result = await option.ConfigureAwait(false);
return await result.OrElse(other).ConfigureAwait(false);
public static Task<Option<T>> OrElse<T>(this Task<Option<T>> option, Task<Option<T>> other)
{
if (other == null) throw new ArgumentNullException(nameof(other));
// Ensure any exception on 'other' is observed even if 'option' is Some and short-circuits.
_ = other.ContinueWith(
t =>
{
var _ = t.Exception;
},
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
// Delegate to the Func<Task<Option<T>>> overload to preserve existing behavior.
return option.OrElse(() => other);

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +99


Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several whitespace-only changes here (extra blank lines and trailing spaces). Please remove trailing whitespace and avoid blank lines containing indentation to keep diffs clean and match typical formatting conventions.

Suggested change

Copilot uses AI. Check for mistakes.

public static IEnumerable<TOut> Choose<T, TOut>(this IEnumerable<T> items, Func<T, Option<TOut>> choose) =>
items.SelectMany(i => choose(i));
items.SelectMany(i => choose(i));
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line appears to have trailing whitespace at the end. Please trim it to avoid noisy diffs and potential formatting/lint issues.

Suggested change
items.SelectMany(i => choose(i));
items.SelectMany(i => choose(i));

Copilot uses AI. Check for mistakes.
Comment on lines +844 to +846
ShouldBeSome42(await none.OrElse(() => Task.FromResult(Some(42))));
return;

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test only exercises the synchronous Option<T>.OrElse(Option<T>), the implicit-value case, and the Func<Task<Option<T>>> overload. It doesn't cover OrElse(Func<Option<T>>) or the Task-based overloads (including the Task<Option<T>> extension methods). Adding assertions for those overloads would prevent regressions in the newly added API surface.

Suggested change
ShouldBeSome42(await none.OrElse(() => Task.FromResult(Some(42))));
return;
// Async overload taking Func<Task<Option<T>>>
ShouldBeSome42(await none.OrElse(() => Task.FromResult(Some(42))));
// Synchronous overload taking Func<Option<T>>
ShouldBeSome42(none.OrElse(() => Some(42)));
// For Some, the factory should not be invoked; value should stay 42
ShouldBeSome42(some.OrElse(() => Some(100)));
// Task-based overloads on Option<T>
ShouldBeSome42(await none.OrElse(Task.FromResult(Some(42))));
// Task<Option<T>> extension method overloads
var noneTask = Task.FromResult(none);
ShouldBeSome42(await noneTask.OrElse(Some(42)));
ShouldBeSome42(await noneTask.OrElse(() => Some(42)));
ShouldBeSome42(await noneTask.OrElse(Task.FromResult(Some(42))));
ShouldBeSome42(await noneTask.OrElse(() => Task.FromResult(Some(42))));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants