Last active
June 20, 2016 18:24
-
-
Save jonashw/37040ee0cd81c8b37831eadacb5a3d54 to your computer and use it in GitHub Desktop.
This is typically how I implement a sum type in C# for day-to-day use. At the bottom is the code I wish I could write instead.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//So much boilerplate! | |
public abstract class UserAccountCreationResult | |
{ | |
private UserAccountCreationResult() { } | |
public UserAccountCreationResult AsBaseType() => this; | |
//Emulate pattern matching | |
public abstract T Match<T>( | |
Func<Success, T> success, | |
Func<WeakPassword, T> weakPassword, | |
Func<Duplicate, T> duplicate, | |
Func<Error, T> error); | |
//This is mainly for easier debugging: | |
public override string ToString() => | |
Match( | |
success => $"Success({success.UserAccountToken})", | |
weakPassword => $"Weak Password({weakPassword.Weakness})", | |
duplicate => $"Duplicate({duplicate.Column})", | |
error => $"Error({error.Message})"); | |
public sealed class Success : UserAccountCreationResult | |
{ | |
public readonly Guid UserAccountToken; | |
internal Success(Guid userAccountToken) | |
{ | |
UserAccountToken = userAccountToken; | |
} | |
public override T Match<T>( | |
Func<Success, T> success, | |
Func<WeakPassword, T> weakPassword, | |
Func<Duplicate, T> duplicate, | |
Func<Error, T> error) => success(this); | |
} | |
public sealed class WeakPassword : UserAccountCreationResult | |
{ | |
public readonly PasswordWeakness Weakness; | |
internal WeakPassword(PasswordWeakness weakness) | |
{ | |
Weakness = weakness; | |
} | |
public override T Match<T>( | |
Func<Success, T> success, | |
Func<WeakPassword, T> weakPassword, | |
Func<Duplicate, T> duplicate, | |
Func<Error, T> error) => weakPassword(this); | |
} | |
public sealed class Duplicate : UserAccountCreationResult | |
{ | |
public readonly UserAccountColumn Column; | |
internal Duplicate(UserAccountColumn column) | |
{ | |
Column = column; | |
} | |
public override T Match<T>( | |
Func<Success, T> success, | |
Func<WeakPassword, T> weakPassword, | |
Func<Duplicate, T> duplicate, | |
Func<Error, T> error) => duplicate(this); | |
} | |
public sealed class Error : UserAccountCreationResult | |
{ | |
public readonly string Message; | |
internal Error(string message) | |
{ | |
Message = message; | |
} | |
public override T Match<T>( | |
Func<Success, T> success, | |
Func<WeakPassword, T> weakPassword, | |
Func<Duplicate, T> duplicate, | |
Func<Error, T> error) => error(this); | |
} | |
} | |
public enum PasswordWeakness | |
{ | |
NoNumber, | |
NoCapitalLetterOrSpecial, | |
TooShort | |
} | |
public enum UserAccountColumn | |
{ | |
Email, Phone | |
} | |
/* Below is the code I'd *like* to write instead. | |
* The SumType attribute indicates that a Match method is to be generated and ToString overridden. | |
* The SumTypeVariant attribute indicates an argument to the generated Match method signature, and implements the Match method. | |
* The SumTypeVariant attribute also generates a constructor for the readonly members of the subclass. | |
*/ | |
[SumType] | |
public abstract class UserAccountCreationResultWithCodeGeneration | |
{ | |
[SumTypeVariant] | |
public sealed class Success : UserAccountCreationResult | |
{ | |
public readonly Guid UserAccountToken; | |
} | |
[SumTypeVariant] | |
public sealed class WeakPassword : UserAccountCreationResult | |
{ | |
public readonly PasswordWeakness Weakness; | |
} | |
[SumTypeVariant] | |
public sealed class Duplicate : UserAccountCreationResult | |
{ | |
public readonly UserAccountColumn Column; | |
} | |
[SumTypeVariant] | |
public sealed class Error : UserAccountCreationResult | |
{ | |
public readonly string Message; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment