AI prompts
base on Easy to use F#-like ~discriminated~ unions for C# with exhaustive compile time matching # OneOf [![NuGet](https://img.shields.io/nuget/v/OneOf?logo=nuget)](https://www.nuget.org/packages/OneOf/) [![GitHub](https://img.shields.io/github/license/mcintyre321/OneOf)](licence.md)
> "Ah! It's like a compile time checked switch statement!" - Mike Giorgaras
## Getting Started
> `install-package OneOf`
This library provides F# style ~discriminated~ unions for C#, using a custom type `OneOf<T0, ... Tn>`. An instance of this type holds a single value, which is one of the types in its generic argument list.
I can't encourage you enough to give it a try! Due to exhaustive matching DUs provide an alternative to polymorphism when you want to have a method with guaranteed behaviour-per-type (i.e. adding an abstract method on a base type, and then implementing that method in each type). It's a really powerful tool, ask any f#/Scala dev! :)
PS If you like OneOf, you might want to check out [ValueOf](https://github.com/mcintyre321/valueof), for one-line Value Object Type definitions.
## Use cases
### As a method return value
The most frequent use case is as a return value, when you need to return different results from a method. Here's how you might use it in an MVC controller action:
```csharp
public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
if (!IsValid(username)) return new InvalidName();
var user = _repo.FindByUsername(username);
if(user != null) return new NameTaken();
var user = new User(username);
_repo.Save(user);
return user;
}
[HttpPost]
public IActionResult Register(string username)
{
OneOf<User, InvalidName, NameTaken> createUserResult = CreateUser(username);
return createUserResult.Match(
user => new RedirectResult("/dashboard"),
invalidName => {
ModelState.AddModelError(nameof(username), $"Sorry, that is not a valid username.");
return View("Register");
},
nameTaken => {
ModelState.AddModelError(nameof(username), "Sorry, that name is already in use.");
return View("Register");
}
);
}
```
#### As an 'Option' Type
It's simple to use OneOf as an `Option` type - just declare a `OneOf<Something, None>`. OneOf comes with a variety of useful Types in the `OneOf.Types` namespace, including `Yes`, `No`, `Maybe`, `Unknown`, `True`, `False`, `All`, `Some`, and `None`.
#### Benefits
- True strongly typed method signature
- No need to return a custom result base type e.g `IActionResult`, or even worse, a non-descriptive type (e.g. object)
- The method signature accurately describes all the potential outcomes, making it easier for consumers to understand the code
- Method consumer HAS to handle all cases (see 'Matching', below)
- You can avoid using ["Exceptions for control flow"](http://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why) antipattern by returning custom Typed error objects
### As a method parameter value
You can use also use `OneOf` as a parameter type, allowing a caller to pass different types without requiring additional overloads. This might not seem that useful for a single parameter, but if you have multiple parameters, the number of overloads required increases rapidly.
```csharp
public void SetBackground(OneOf<string, ColorName, Color> backgroundColor) { ... }
//The method above can be called with either a string, a ColorName enum value or a Color instance.
```
## Matching
You use the `TOut Match(Func<T0, TOut> f0, ... Func<Tn,TOut> fn)` method to get a value out. Note how the number of handlers matches the number of generic arguments.
### Advantages over `switch` or `if` or `exception` based control flow:
This has a major advantage over a switch statement, as it
- requires every parameter to be handled
- No fallback - if you add another generic parameter, you HAVE to update all the calling code to handle your changes.
In brown-field code-bases this is incredibly useful, as the default handler is often a runtime `throw NotImplementedException`, or behaviour that wouldn't suit the new result type.
E.g.
```csharp
OneOf<string, ColorName, Color> backgroundColor = ...;
Color c = backgroundColor.Match(
str => CssHelper.GetColorFromString(str),
name => new Color(name),
col => col
);
_window.BackgroundColor = c;
```
There is also a .Switch method, for when you aren't returning a value:
```csharp
OneOf<string, DateTime> dateValue = ...;
dateValue.Switch(
str => AddEntry(DateTime.Parse(str), foo),
int => AddEntry(int, foo)
);
```
### TryPickš„ method
As an alternative to `.Switch` or `.Match` you can use the `.TryPickš„` methods.
```csharp
//TryPickš„ methods for OneOf<T0, T1, T2>
public bool TryPickT0(out T0 value, out OneOf<T1, T2> remainder) { ... }
public bool TryPickT1(out T1 value, out OneOf<T0, T2> remainder) { ... }
public bool TryPickT2(out T2 value, out OneOf<T0, T1> remainder) { ... }
```
The return value indicates if the OneOf contains a Tš„ or not. If so, then `value` will be set to the inner value from the OneOf. If not, then the remainder will be a OneOf of the remaining generic types. You can use them like this:
```csharp
IActionResult Get(string id)
{
OneOf<Thing, NotFound, Error> thingOrNotFoundOrError = GetThingFromDb(string id);
if (thingOrNotFoundOrError.TryPickT1(out NotFound notFound, out var thingOrError)) //thingOrError is a OneOf<Thing, Error>
return StatusCode(404);
if (thingOrError.TryPickT1(out var error, out var thing)) //note that thing is a Thing rather than a OneOf<Thing>
{
_logger.LogError(error.Message);
return StatusCode(500);
}
return Ok(thing);
}
```
### Reusable OneOf Types using OneOfBase
You can declare a OneOf as a type, either for reuse of the type, or to provide additional members, by inheriting from `OneOfBase`. The derived class will inherit the `.Match`, `.Switch`, and `.TryPickš„` methods.
```csharp
public class StringOrNumber : OneOfBase<string, int>
{
StringOrNumber(OneOf<string, int> _) : base(_) { }
// optionally, define implicit conversions
// you could also make the constructor public
public static implicit operator StringOrNumber(string _) => new StringOrNumber(_);
public static implicit operator StringOrNumber(int _) => new StringOrNumber(_);
public (bool isNumber, int number) TryGetNumber() =>
Match(
s => (int.TryParse(s, out var n), n),
i => (true, i)
);
}
StringOrNumber x = 5;
Console.WriteLine(x.TryGetNumber().number);
// prints 5
x = "5";
Console.WriteLine(x.TryGetNumber().number);
// prints 5
x = "abcd";
Console.WriteLine(x.TryGetNumber().isNumber);
// prints False
```
### OneOfBase Source Generation
You can automatically generate `OneOfBase` hierarchies using `GenerateOneOfAttribute` and partial class that extends `OneOfBase` using
a Source Generator (thanks to @romfir for the contribution :D). Install it via
> Install-Package OneOf.SourceGenerator
and then define a stub like so:
```csharp
[GenerateOneOf]
public partial class StringOrNumber : OneOfBase<string, int> { }
```
During compilation the source generator will produce a class implementing the OneOfBase boiler plate code for you. e.g.
```csharp
public partial class StringOrNumber
{
public StringOrNumber(OneOf.OneOf<System.String, System.Int32> _) : base(_) { }
public static implicit operator StringOrNumber(System.String _) => new StringOrNumber(_);
public static explicit operator System.String(StringOrNumber _) => _.AsT0;
public static implicit operator StringOrNumber(System.Int32 _) => new StringOrNumber(_);
public static explicit operator System.Int32(StringOrNumber _) => _.AsT1;
}
```
", Assign "at most 3 tags" to the expected json: {"id":"2557","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"