Skip to content
Merged
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
120 changes: 110 additions & 10 deletions src/libanvl.Opt/Opt.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using libanvl.Exceptions;
using System.Collections;
using System;
using System.Runtime.Serialization;
using System.Collections;
using libanvl.Exceptions;

namespace libanvl;

Expand Down Expand Up @@ -33,6 +31,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 +47,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 +61,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 All @@ -78,10 +84,85 @@ public static Opt<T> Filter<T>(this Opt<T> opt, Func<T, bool> predicate) where T
/// <param name="opt">The option to transform.</param>
/// <param name="fn">The function to transform the value.</param>
/// <returns>The transformed option.</returns>
public static Opt<U> AndThen<T, U>(this Opt<T> opt, Func<T, Opt<U>> fn) where T : notnull where U : notnull
public static Opt<U> AndThen<T, U>(this Opt<T> opt, Func<T, Opt<U>> fn)
where T : notnull
where U : notnull
{
return opt.IsSome ? fn(opt.Unwrap()) : Opt<U>.None;
}

/// <summary>
/// Transforms the option using a function that returns another option.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <typeparam name="U">The type of the result.</typeparam>
/// <param name="opt">The option to transform.</param>
/// <param name="fn">The function to transform the value.</param>
/// <returns>The transformed option.</returns>
public static Opt<U> AndThen<T, U>(this Opt<T> opt, Func<T, U?> fn)
where T : notnull
where U : notnull
{
return opt.IsSome ? From(fn(opt.Unwrap())) : Opt<U>.None;
}

/// <summary>
/// Transforms the option using a function that returns another option.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <typeparam name="U">The type of the result.</typeparam>
/// <param name="opt">The option to transform.</param>
/// <param name="fn">The function to transform the value.</param>
/// <returns>The transformed option.</returns>
public static Opt<U> AndThen<T, U>(this Opt<T> opt, Func<T, U?> fn)
where T : notnull
where U : struct
{
return opt.IsSome ? From(fn(opt.Unwrap())) : Opt<U>.None;
}

/// <summary>
/// Returns the first option if it has a value, otherwise returns the result of the specified function.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <typeparam name="U">The type of the state.</typeparam>
/// <param name="opt">The option to check.</param>
/// <param name="state">The state to pass to the function if the option does not have a value.</param>
/// <param name="fn">The function to invoke if the option does not have a value.</param>
/// <returns>The first option if it has a value, otherwise the result of the function.</returns>
public static Opt<T> OrThen<T, U>(this Opt<T> opt, U state, Func<U, T?> fn)
where T : notnull
{
return opt.IsSome ? opt : From(fn(state));
}

/// <summary>
/// Returns the first option if it has a value, otherwise returns the result of the specified function.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <typeparam name="U">The type of the state.</typeparam>
/// <param name="opt">The option to check.</param>
/// <param name="state">The state to pass to the function if the option does not have a value.</param>
/// <param name="fn">The function to invoke if the option does not have a value.</param>
/// <returns>The first option if it has a value, otherwise the result of the function.</returns>
public static Opt<T> OrThen<T, U>(this Opt<T> opt, U state, Func<U, T?> fn)
where T : struct
{
return opt.IsSome ? opt : From(fn(state));
}

/// <summary>
/// Returns the first option if it has a value, otherwise returns the second option.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="left">The first option.</param>
/// <param name="right">The second option.</param>
/// <returns>The first option if it has a value, otherwise the second option.</returns>
public static Opt<T> OrThen<T>(this Opt<T> left, Opt<T> right)
where T : notnull
{
return left.IsSome ? left : right;
}
}

/// <summary>
Expand All @@ -102,7 +183,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 +329,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
122 changes: 122 additions & 0 deletions test/libanvl.Opt.Test/OptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public void AndThen_ReturnsNone_WhenNone()
var result = opt.AndThen(x => Opt.Some(x * 2));
Assert.True(result.IsNone);
}

[Fact]
public void CompareTo_ReturnsNegative_WhenThisIsSomeAndOtherIsNone()
{
Expand Down Expand Up @@ -268,4 +269,125 @@ 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 AndThen_WithNullableFunc_ReturnsTransformedOption_WhenSome()
{
var opt = Opt.Some(5);
var result = opt.AndThen(x => (int?)(x * 2));
Assert.True(result.IsSome);
Assert.Equal(10, result.Unwrap());
}

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

[Fact]
public void OrThen_WithFunc_ReturnsFirstOption_WhenSome()
{
var opt = Opt.Some(5);
var result = opt.OrThen("state", state => (int?)10);
Assert.True(result.IsSome);
Assert.Equal(5, result.Unwrap());
}

[Fact]
public void OrThen_WithFunc_ReturnsTransformedOption_WhenNone()
{
var opt = Opt<int>.None;
var result = opt.OrThen("state", state => (int?)10);
Assert.True(result.IsSome);
Assert.Equal(10, result.Unwrap());
}

[Fact]
public void OrThen_WithOption_ReturnsFirstOption_WhenSome()
{
var opt1 = Opt.Some(5);
var opt2 = Opt.Some(10);
var result = opt1.OrThen(opt2);
Assert.True(result.IsSome);
Assert.Equal(5, result.Unwrap());
}

[Fact]
public void OrThen_WithOption_ReturnsSecondOption_WhenNone()
{
var opt1 = Opt<int>.None;
var opt2 = Opt.Some(10);
var result = opt1.OrThen(opt2);
Assert.True(result.IsSome);
Assert.Equal(10, result.Unwrap());
}

[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