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

add new method WithType to add types to a OneOf #171

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Pxtl
Copy link

@Pxtl Pxtl commented Feb 17, 2024

In order that functions that return OneOf can call other functions that return
OneOf, the .WithType() method allows you to return existing OneOf structs but
with added types.

Note: Unfortunately because of the extended assembly design, OneOf types with
over 8 parameters cannot use .WithType.

public OneOf<string, double> ParseDouble(string input) {
    if (double.TryParse(input, out var result)) {
        return result;
    } else {
        return input;
    }
}

public OneOf<string, double, DateTime, int> ParseDoubleOrUTCDateOrInt(string input) {
    if (DateTime.TryParseExact(input, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var dateResult)) {
        return dateResult;
    }
    else if (int.TryParse(input, out var intResult)) {
        return intResult;
    } else {
        return ParseDouble(input).WithType<DateTime>().WithType<int>();
    }
}

resolves #150

- also added the README to the solution
@@ -49,13 +50,13 @@ namespace OneOf
readonly int _index;

{IfStruct( // constructor
$@"OneOf(int index, {RangeJoined(", ", j => $"T{j} value{j} = default")})
$@"internal OneOf(int index, {RangeJoined(", ", j => $"T{j} value{j} = default")})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why add internal to these, it will break anyone's code who has inherited from these with these constructors

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's necessary. If you look in WithType, it uses the constructor of OneOfTN+1. This is also why WithType doesn't work with large size OneOfs - it can't access the constructors of the next size beyond extendedSizeLimit - 1 because those classes are located in a different assembly.

Copy link

@jacob7395 jacob7395 Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, ye I see that for this constructor now, but the $@"protected internal OneOfBase(OneOf<{genericArg}> input)1 can stay as just protected?

That way it wont effect anyone who is inheriting.

@@ -77,6 +78,12 @@ namespace OneOf

public int Index => _index;

{((i < extendedSizeLimit - 1) ?
// can go up to the limit before extended because OneOfT8 cannot see OneOfT9
$@"public OneOf<{genericArg}, TNew> WithType<TNew>() =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a good method but, I am not sure if this interface meets the use cases I have in mind. When this is used would it not makes sense to provide a value and set the index to that value.

I think the method should at least be provided.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean - so in your case you're using this as a convenience object to construct a new object with a new WithType and its own value, and we're just going to throw out the old value-content.

That makes sense. I'd have to see how that works with the existing methods of OneOf. Is there some equivalent "use an existing object for a convenience-object for constructing a new one" thing to build off of?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking it over, maybe I should be redoing this as .WithTN+1 so that it becomes something like

OneOf<T1, T2, T3> myOneOf = SomeFunc();
return myOneOf.WithT4<SomeClass>();

Then the case you describe could be done as a .FromTN+1; so OneOfT3 would have a .FromT4 for cases where you want to expand a T3 by one.

Or maybe just call that .FromNewType, which would make sense as a counterpart to .WithType, althought I don't know how much the .FromTN things get used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm being silly, you can't call a static method from an instance so I think the only time you could ever use .FromTN+1 would be after literally defining var myOneOf = OneOf<T1, T2, T3>.FromT4<T4>(someValue). Why would you ever do that?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wat thinking something like

public OneOf<T0, TNew> WithType<TNew>(TNew value) => value;

This seems to work;

        {((i < extendedSizeLimit - 1) ?
            // can go up to the limit before extended because OneOfT8 cannot see OneOfT9
            $@"public OneOf<{genericArg}, TNew> WithType<TNew>(TNew value) => value;
        ":"")}

Could still keep the WithType that doesn't take a value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I was being way too verbose in my old replies, thinking out loud. My point: in this case you're essentially constructing a new OneOf and throwing out the old value. I mean, I see how it works but I don't get what it's for.

@mcintyre321
Copy link
Owner

mcintyre321 commented Oct 29, 2024 via email

@jacob7395
Copy link

jacob7395 commented Oct 29, 2024

Hi, thanks for using OneOf enough enough to extend it. I think this feature would be better off in an external package, rather than in the main project, which I'm trying to keep lean. I can add a section to the readme with a link if you create your own project.

The issue is functionality like this requires the use a private (made internal with this PR), what is your reason behind trying to keep it lean. Functionality like this PR are more syntactic sugar.

@Pxtl
Copy link
Author

Pxtl commented Oct 29, 2024

@mcintyre321

Hi, thanks for using OneOf enough enough to extend it. I think this feature would be better off in an external package, rather than in the main project, which I'm trying to keep lean. I can add a section to the readme with a link if you create your own project.

I mean it's possible but the API that makes WithType pretty slick to implement and also pretty performant is private. Without a public constructor OneOf<T0...TN>(int index, T0 value0, ... TN valueN) it becomes much less practical. Are there other forms of this you might be interested in? I might play with the reverse approach... allowing "smaller" one-ofs have an implicit cast to "larger" one-ofs as a way to implement the same feature.

@Pxtl
Copy link
Author

Pxtl commented Oct 29, 2024

@mcintyre321

I've added an alternate typecast-based approach here:

#185

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

Successfully merging this pull request may close these issues.

Nested calls to multiple methods returning OneOf<T1, T2, ...>
3 participants