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

[Union] attribute on an partial record instead of interface? #17

Closed
saul opened this issue Jun 24, 2022 · 3 comments
Closed

[Union] attribute on an partial record instead of interface? #17

saul opened this issue Jun 24, 2022 · 3 comments

Comments

@saul
Copy link
Contributor

saul commented Jun 24, 2022

Example usage

[Union]
public partial record Result
{
    partial record Ok(string Message);

    partial record Error(Exception Exception)
    {
        // Can override specific methods on the generated types
        public override string ToString() => Exception.ToString();

        // ...or add new ones without resorting to extension methods
        public bool CanIgnore => Exception is OperationCanceledException;
    }
}

// Instantiate the Error case directly:
var result = new Result.Error(new Exception("hi :)"));

// Use the factory methods that return `Result`:
var otherResult = Result.NewError(new Exception("another way"));

result.Match(
    (Result.Ok _) => 1,
    (Result.Error e) => e.CanIgnore ? 1 : 0);

// Can use named arguments to make `match` more explicit:
result.Match(
    error: e => e.CanIgnore ? 1 : 0,
    ok: m => m.Message.Length);

Benefits

  • With the generated code below, it is impossible to create another type that derives from Result as it has a private constructor.
  • It is possible to add your own method to the Result class and the case classes directly rather than having to write them as extension methods.
  • Untested hypothesis, but I imagine the JIT would generate better code with an abstract Match method, as opposed to the extension method that you have currently.

Source generator produces

abstract partial record Result
{
    // No other classes can derive from this class
    private Result() {}

    public static Result NewOk(string message) => new Ok(message);
    public static Result NewError(Exception exception) => new Error(exception);

    public abstract T Match<T>(Func<Ok, T> ok, Func<Error, T> error);

    public sealed partial record Ok : Result
    {
        public override T Match<T>(Func<Ok, T> ok, Func<Error, T> error) =>
            ok(this);
    }

    public sealed partial record Error : Result
    {
        public override T Match<T>(Func<Ok, T> ok, Func<Error, T> error) =>
            error(this);
    }
}
@saul saul changed the title [Union] attribute on an abstract record instead of interface? [Union] attribute on an partial record instead of interface? Jun 24, 2022
@domn1995
Copy link
Owner

domn1995 commented Jun 24, 2022

I really like this idea! Seems more powerful and easier to generate sources for.

Implementation note
This should probably be implemented as an additional generator that gets triggered when an abstract record marked with the Union attribute changes. Don't think it would be a good idea to try to fit this functionality into DiscriminatedUnionGenerator.

If this method proves better, we can eventually deprecate the marked interface generator in favor of this new method.

@domn1995
Copy link
Owner

domn1995 commented Jun 24, 2022

With this implementation we should also be able to do:

using static Result;

// Instantiate the Error case directly:
var result = new Error(new Exception("hi :)"));

// Use the factory methods that return `Result`:
var otherResult = NewError(new Exception("another way"));

This was referenced Jun 26, 2022
@domn1995
Copy link
Owner

Closing as the core functionality is implemented by #21 and #25 and released in https://github.com/domn1995/dunet/releases/tag/v0.5.0

Will track factory method generation in #12.

Thanks so much for this awesome suggestion!

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