Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
FreeApophis opened this issue Oct 21, 2022 · 4 comments

Comments

@FreeApophis
Copy link
Member

FreeApophis commented Oct 21, 2022

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 FreeApophis changed the title Should we have a Cast<T> function on our Monads (for the main value) like IEnumerable? Should we have a Cast<T> function on our Monads (for the main value) like IEnumerable<T>? Oct 21, 2022
@bash
Copy link
Member

bash commented Oct 21, 2022

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
Copy link
Member Author

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:

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

@bash
Copy link
Member

bash commented Oct 30, 2023

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
Copy link
Member Author

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

I reviewed the implementation it's merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants