Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions src/libanvl.Opt/Opt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ public static class Opt
/// <returns>An <see cref="Opt{T}"/> containing the value if not null, otherwise <see cref="Opt{T}.None"/>.</returns>
public static Opt<T> From<T>(T? value) where T : notnull => value is null ? Opt<T>.None : Some(value);

/// <summary>
/// Creates an <see cref="Opt{T}"/> from a nullable value.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The nullable value to wrap.</param>
/// <returns>An <see cref="Opt{T}"/> containing the value if not null, otherwise <see cref="Opt{T}.None"/>.</returns>
public static Opt<T> From<T>(T? value) where T : struct => value.HasValue ? Some(value.Value) : Opt<T>.None;

/// <summary>
/// Combines two options into an option containing a tuple of their values if both are present.
/// </summary>
Expand All @@ -41,7 +49,7 @@ public static class Opt
/// <param name="opt1">The first option.</param>
/// <param name="opt2">The second option.</param>
/// <returns>An option containing a tuple of the values if both are present, otherwise none.</returns>
public static Opt<(T, U)> Zip<T, U>(Opt<T> opt1, Opt<U> opt2)
public static Opt<(T, U)> Zip<T, U>(this Opt<T> opt1, Opt<U> opt2)
where T : notnull
where U : notnull
{
Expand All @@ -55,7 +63,7 @@ public static class Opt
/// </summary>
/// <param name="opt">The option to flatten.</param>
/// <returns>The flattened option.</returns>
public static Opt<T> Flatten<T>(Opt<Opt<T>> opt)
public static Opt<T> Flatten<T>(this Opt<Opt<T>> opt)
where T : notnull => opt.IsSome ? opt.Unwrap() : Opt<T>.None;

/// <summary>
Expand Down Expand Up @@ -102,7 +110,7 @@ public static Opt<U> AndThen<T, U>(this Opt<T> opt, Func<T, Opt<U>> fn) where T
/// </summary>
/// <param name="value">The value to wrap.</param>
/// <exception cref="OptException">Thrown if the value is null.</exception>
public Opt(T value)
internal Opt(T value)
{
OptException.ThrowIfNull(value);
_value = value;
Expand Down Expand Up @@ -248,10 +256,29 @@ public TResult Match<TResult>(Func<T, TResult> some, Func<TResult> none) => IsSo
/// <typeparam name="U">The type of the result.</typeparam>
/// <param name="fn">The function to transform the value.</param>
/// <returns>An <see cref="Opt{U}"/> containing the transformed value or none.</returns>
public Opt<U> Select<U>(Func<T, U> fn) where U : notnull => IsSome
? new(fn(OptException.ThrowInternalErrorIfNull(_value)))
public Opt<U> Select<U>(Func<T, U?> fn) where U : notnull => IsSome
? Opt.From(fn(OptException.ThrowInternalErrorIfNull(_value)))
: Opt<U>.None;

/// <summary>
/// Transforms the value if present using the specified function.
/// </summary>
/// <typeparam name="U">The type of the result.</typeparam>
/// <param name="fn">The function to transform the value.</param>
/// <returns>An <see cref="Opt{U}"/> containing the transformed value or none.</returns>
public Opt<U> Select<U>(Func<T, U?> fn) where U: struct => IsSome
? Opt.From(fn(OptException.ThrowInternalErrorIfNull(_value)))
: Opt<U>.None;

/// <summary>
/// Filters the option based on a predicate.
/// </summary>
/// <param name="predicate">The predicate to apply.</param>
/// <returns>The option if the predicate is true, otherwise none.</returns>
public Opt<T> Where(Func<T, bool> predicate) => IsSome && predicate(OptException.ThrowInternalErrorIfNull(_value))
? this
: None;

/// <summary>
/// Casts the value to the specified type if present.
/// </summary>
Expand Down
74 changes: 74 additions & 0 deletions test/libanvl.Opt.Test/OptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,78 @@ public void GetHashCode_ReturnsSameHashCode_ForNone()
var opt2 = Opt<int>.None;
Assert.Equal(opt1.GetHashCode(), opt2.GetHashCode());
}

[Fact]
public void Select_ReturnsTransformedOption_WhenSome()
{
var opt = Opt.Some(5);
var result = opt.Select(x => x * 2);
Assert.True(result.IsSome);
Assert.Equal(10, result.Unwrap());
}

[Fact]
public void Select_ReturnsNone_WhenTransformedValueIsNull()
{
var opt = Opt.Some("Hello");
var result = opt.Select<string>(x => null);
Assert.True(result.IsNone);
}

[Fact]
public void Select_ReturnsNone_WhenTransformedValueIsNullValueType()
{
Opt<int> opt = 5;
var result = opt.Select<int>(_ => null);
Assert.True(result.IsNone);
}

[Fact]
public void Select_ReturnsNone_WhenNone()
{
var opt = Opt<int>.None;
var result = opt.Select(x => x * 2);
Assert.True(result.IsNone);
}

[Fact]
public void Where_ReturnsSome_WhenPredicateIsTrue()
{
var opt = Opt.Some(5);
var result = opt.Where(x => x > 3);
Assert.True(result.IsSome);
}

[Fact]
public void Where_ReturnsNone_WhenPredicateIsFalse()
{
var opt = Opt.Some(5);
var result = opt.Where(x => x < 3);
Assert.True(result.IsNone);
}

[Fact]
public void Where_ReturnsNone_WhenOptionIsNone()
{
var opt = Opt<int>.None;
var result = opt.Where(x => x > 3);
Assert.True(result.IsNone);
}

[Fact]
public void Cast_ReturnsSome_WhenCastIsValid()
{
var opt = Opt.Some((object)5);
var result = opt.Cast<int>();
Assert.True(result.IsSome);
Assert.Equal(5, result.Unwrap());
}

[Fact]
public void Cast_ReturnsNone_WhenCastIsInvalid()
{
var opt = Opt.Some((object)"string");
var result = opt.Cast<int>();
Assert.True(result.IsNone);
}
}
Loading