funcky icon indicating copy to clipboard operation
funcky copied to clipboard

Should we have a `Cast<T>` function on our Monads (for the main value) like `IEnumerable<T>`?

Open FreeApophis opened this issue 3 years ago • 4 comments

The alternatives currently:

var maybeObject = maybeInt.Select(value => value as object),
// or
var maybeObject = maybeInt.Select<object>(value => value),
  • Functional.Identity cannot be used because of the differing return type.
  • If we could make Option<T> co-variant, this case would be a non-issue.

FreeApophis avatar Oct 21 '22 09:10 FreeApophis

I would love to have something like Enumerable.Cast. Something I dislike about Cast however, is that there is no compile-time checks.

Edit: I just realized that my extension method isn't useful because you'd always had to specify both types :/

~~I feel like most cases you want to cast upwards, so a CastUp or Upcast method would be helpful to avoid mistakes:~~

public static Option<TResult> Upcast<TItem, TResult>(this Option<TItem> option)
    where TItem : notnull, TResult
    where TResult : notnull => ...

~~ImmutableArray has an extension method like this: ImmutableArray.CastUp~~

bash avatar Oct 21 '22 11:10 bash

The Upcast is only possible with the two type parameters, but you could theoretically do something like this, to do type-deduction on the Option.

class UpCast<TResult>
    where TResult : notnull
{
    public From<TItem>(Option<T> option)
        where TItem : notnull, TResult
    {
        // ...
    }
}

Here are three possible variants which would be internally to the Option<TItem> class, which avoids the type-deduction problems.

public readonly partial struct Option<TItem>
    where TItem : notnull
{
    [Pure]
    public Option<TResult> Cast<TResult>()
        where TResult : class
        => _hasItem
            ? Option.Some((TResult)(object)_item)
            : Option<TResult>.None;

    [Pure]
    public Option<TResult> DownCast<TResult>()
        where TResult : TItem
        => _hasItem
            ? Option.Some((TResult)_item)
            : Option<TResult>.None;

    [Pure]
    public Option<TResult> As<TResult>()
        where TResult : class
        => _hasItem
            ? Option.FromNullable(_item as TResult)
            : Option<TResult>.None;
}

Proposal:

Only implement the As function and only on Option<TItem>

Reasons:

  • This is very natural and nobody will expect an exception.
  • The other monads do not have None as fallback and there is no natural semantic I could think of.
  • What I see used is mostly the Idea of casting in a safe way like this.

FreeApophis avatar Oct 25 '23 13:10 FreeApophis

The upcast idea looks really cool 😍

I think we might be able to extend that to Either and Result as upcasts are infallible, right?

public static class UpCast<TResult>
    where TResult : notnull
{
    public static Option<TResult> From<TItem>(Option<TItem> option)
        where TItem : notnull, TResult
        => throw new NotImplementedException();

    public static Either<TLeft, TResult> From<TLeft, TRight>(Either<TLeft, TRight> either)
        where TLeft : notnull
        where TRight : notnull, TResult
        => throw new NotImplementedException();

    // Same thing for Result
}

bash avatar Oct 30 '23 10:10 bash

Yes the UpCast cannot fail since we do type check statically.

I reviewed the implementation it's merged.

FreeApophis avatar Nov 16 '23 09:11 FreeApophis

I run into problems in which a DownCast would be helpful more often, thats why I tried to find an acceptable solution.

I propose a solution for DownCast<T> which looks very similar to UpCast<T> and works for Option, Either and Result.

PR #794

FreeApophis avatar May 29 '24 14:05 FreeApophis