polyadic / funcky

Funcky is a Functional Library for C#
https://polyadic.github.io/funcky
Apache License 2.0
19 stars 4 forks source link

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

Closed FreeApophis closed 5 months ago

FreeApophis commented 2 years ago

The alternatives currently:

var maybeObject = maybeInt.Select(value => value as object),
// or
var maybeObject = maybeInt.Select<object>(value => value),
bash commented 2 years ago

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~~

FreeApophis commented 1 year ago

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 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

Reasons:

bash commented 1 year ago

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
}
FreeApophis commented 1 year ago

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

I reviewed the implementation it's merged.

FreeApophis commented 5 months ago

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, Eitherand Result.

PR #794