Skip to content

Commit

Permalink
Support Downcast on the Monadic Types (Option, Either, Result)
Browse files Browse the repository at this point in the history
* with tests
  • Loading branch information
FreeApophis committed May 29, 2024
1 parent dac97a4 commit d0e1802
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 1 deletion.
78 changes: 78 additions & 0 deletions Funcky.Test/DownCastTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace Funcky.Test;

public sealed class DownCastTest
{
[Fact]
public void DownCastToObjectOfRuntimeTypeReturnsSomeOfTheRequestedType()
{
var option = Option.Some<object>("Hello world!");

FunctionalAssert.Some("Hello world!", DownCast<string>.From(option));
}

[Fact]
public void DownCastToWrongObjectOfRuntimeTypeReturnsNone()
{
var option = Option.Some(new object());

FunctionalAssert.None(DownCast<string>.From(option));
}

[Fact]
public void DownCastFromANoneAlwaysGivesNone()
{
var option = Option<object>.None;

FunctionalAssert.None(DownCast<string>.From(option));
}

[Fact]
public void DownCastToObjectOfRuntimeTypeReturnsRightOfTheRequestedType()
{
var either = Either<string, object>.Right("Hello world!");

FunctionalAssert.Right("Hello world!", DownCast<string>.From(either, () => "failed cast!"));
}

[Fact]
public void DownCastToWrongObjectOfRuntimeTypeReturnsLeft()
{
var either = Either<string, object>.Right(new object());

FunctionalAssert.Left("failed cast!", DownCast<string>.From(either, () => "failed cast!"));
}

[Fact]
public void DownCastFromALeftAlwaysGivesTheOldLeft()
{
var either = Either<string, object>.Left("initial left state!");

FunctionalAssert.Left("initial left state!", DownCast<string>.From(either, () => "failed cast!"));
}

[Fact]
public void DownCastToObjectOfRuntimeTypeReturnsOkOfTheRequestedType()
{
var result = Result.Ok<object>("Hello world!");

FunctionalAssert.Ok("Hello world!", DownCast<string>.From(result));
}

[Fact]
public void DownCastToWrongObjectOfRuntimeTypeReturnsError()
{
var result = Result.Ok(new object());

var exception = FunctionalAssert.Error(DownCast<string>.From(result));
Assert.IsType<InvalidCastException>(exception);
}

[Fact]
public void DownCastFromAnErrorAlwaysGivesTheOldError()
{
var result = Result<object>.Error(new FileNotFoundException());

var exception = FunctionalAssert.Error(DownCast<string>.From(result));
Assert.IsType<FileNotFoundException>(exception);
}
}
35 changes: 35 additions & 0 deletions Funcky/DownCast.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Funcky;

public static class DownCast<TResult>
where TResult : class
{
public static Option<TResult> From<TItem>(Option<TItem> option)
where TItem : class
=> option.SelectMany(OptionDownCast);

public static Result<TResult> From<TItem>(Result<TItem> result)
where TItem : class
=> result.SelectMany(ResultDownCast);

public static Either<TLeft, TResult> From<TLeft, TRight>(Either<TLeft, TRight> either, Func<TLeft> failedCast)
where TRight : class
where TLeft : notnull
=> either.SelectMany(right => EitherDownCast(failedCast, right));

private static Option<TResult> OptionDownCast<TItem>(TItem item)
where TItem : class
=> Option.FromNullable(item as TResult);

private static Result<TResult> ResultDownCast<TValidResult>(TValidResult result)
where TValidResult : class
=> result as TResult is { } validResult
? Result.Ok(validResult)
: Result<TResult>.Error(new InvalidCastException());

private static Either<TLeft, TResult> EitherDownCast<TLeft, TRight>(Func<TLeft> failedCast, TRight right)
where TRight : class
where TLeft : notnull
=> right as TResult is { } result
? Either<TLeft, TResult>.Right(result)
: Either<TLeft, TResult>.Left(failedCast());
}
5 changes: 4 additions & 1 deletion Funcky/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
#nullable enable

Funcky.DownCast<TResult>
static Funcky.DownCast<TResult>.From<TItem>(Funcky.Monads.Option<TItem!> option) -> Funcky.Monads.Option<TResult!>
static Funcky.DownCast<TResult>.From<TItem>(Funcky.Monads.Result<TItem!> result) -> Funcky.Monads.Result<TResult!>
static Funcky.DownCast<TResult>.From<TLeft, TRight>(Funcky.Monads.Either<TLeft, TRight!> either, System.Func<TLeft>! failedCast) -> Funcky.Monads.Either<TLeft, TResult!>

0 comments on commit d0e1802

Please sign in to comment.