Skip to content

Instantly share code, notes, and snippets.

@johnscott999
Last active May 30, 2021 04:53
Show Gist options
  • Save johnscott999/e63f5eb792e8a3ffeda1ea8d0d123c6f to your computer and use it in GitHub Desktop.
Save johnscott999/e63f5eb792e8a3ffeda1ea8d0d123c6f to your computer and use it in GitHub Desktop.
SSW: What I've learned from 20 years of programming in C# with Joe Albahari - Either Code Gen
#region IEither interface
public interface IEither
{
int PopulatedType { get; }
object Value { get; }
}
#endregion IEither interface
#region Either<T1, T2>
public record Either<T1, T2> : IEither
{
#region Implicit Conversion From Value
public static implicit operator Either<T1, T2>(T1 value) => new Either<T1, T2>(value);
public static implicit operator Either<T1, T2>(T2 value) => new Either<T1, T2>(value);
#endregion Implicit Conversion From Value
#region Implicit Convertsion By Type Equivalance
public static implicit operator Either<T1, T2> (Either<T2, T1> other)
{
int[] map = new [] { 2, 1 };
return new Either<T1, T2>(map[other._populatedType - 1], other._value);
}
#endregion Implicit Convertsion By Type Equivalance
#region Constructors
public Either (T1 value) { _value = value; _populatedType = 1; }
public Either (T2 value) { _value = value; _populatedType = 2; }
#endregion Constructors
#region IEither Implementation
int _populatedType;
object _value;
int IEither.PopulatedType => _populatedType;
object IEither.Value => _value;
Either (int populatedType, object value) => (_populatedType, _value) = (populatedType, value);
#endregion IEither Implementation
#region Value Casts
T1 AsT1 => (T1)_value;
T2 AsT2 => (T2)_value;
#endregion Value Casts
#region Switch (method)
public void Switch (Action<T1> ifT1, Action<T2> ifT2)
{
switch(_populatedType)
{
case 1: ifT1(AsT1); break;
case 2: ifT2(AsT2); break;
default: throw new InvalidOperationException();
}
}
#endregion Switch (method)
#region Nonsimplifying Match
public Either<TResult1, T2> Match<TResult1> (Func<T1, TResult1> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, TResult1> Match<TResult1>
(Func<T1, TResult1> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, TResult2> Match<TResult2> (Func<T2, TResult2> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, TResult2> Match<TResult2>
(Func<T2, TResult2> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
2 => AsT2,
_ => throw new InvalidOperationException()
};
#endregion Nonsimplifying Match
#region Simplifying Match
public T2 Match
(Func<T1, T2> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T1, T2> Match
(Func<T1, T2> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public T1 Match
(Func<T2, T1> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
_ => throw new InvalidOperationException()
};
public Either<T1, T2> Match
(Func<T2, T1> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
2 => AsT2,
_ => throw new InvalidOperationException()
};
#endregion Simplifying Match
#region If (methods)
public bool If (out T1 @if) => If (out @if, out _);
public bool If(out T1 @if, out T2 @else)
{
switch (_populatedType)
{
case 1:
@if = AsT1;
@else = default;
return true;
case 2:
@if = default;
@else = AsT2;
return false;
default:
throw new InvalidOperationException();
}
}
public bool If (out T2 @if) => If (out @if, out _);
public bool If(out T2 @if, out T1 @else)
{
switch (_populatedType)
{
case 1:
@if = default;
@else = AsT1;
return false;
case 2:
@if = AsT2;
@else = default;
return true;
default:
throw new InvalidOperationException();
}
}
#endregion If (methods)
#region ToString
public override string ToString() => _populatedType switch
{
1 => typeof (T1).Name + ":" + AsT1,
2 => typeof (T2).Name + ":" + AsT2,
_ => throw new InvalidOperationException()
};
#endregion ToString
}
#endregion Either<T1, T2>
#region Either<T1, T2, T3>
public record Either<T1, T2, T3> : IEither
{
#region Implicit Conversion From Value
public static implicit operator Either<T1, T2, T3>(T1 value) => new Either<T1, T2, T3>(value);
public static implicit operator Either<T1, T2, T3>(T2 value) => new Either<T1, T2, T3>(value);
public static implicit operator Either<T1, T2, T3>(T3 value) => new Either<T1, T2, T3>(value);
#endregion Implicit Conversion From Value
#region Implicit Convertsion By Type Equivalance
public static implicit operator Either<T1, T2, T3> (Either<T1, T3, T2> other)
{
int[] map = new [] { 1, 3, 2 };
return new Either<T1, T2, T3>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3> (Either<T2, T1, T3> other)
{
int[] map = new [] { 2, 1, 3 };
return new Either<T1, T2, T3>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3> (Either<T2, T3, T1> other)
{
int[] map = new [] { 2, 3, 1 };
return new Either<T1, T2, T3>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3> (Either<T3, T1, T2> other)
{
int[] map = new [] { 3, 1, 2 };
return new Either<T1, T2, T3>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3> (Either<T3, T2, T1> other)
{
int[] map = new [] { 3, 2, 1 };
return new Either<T1, T2, T3>(map[other._populatedType - 1], other._value);
}
#endregion Implicit Convertsion By Type Equivalance
#region Constructors
public Either (T1 value) { _value = value; _populatedType = 1; }
public Either (T2 value) { _value = value; _populatedType = 2; }
public Either (T3 value) { _value = value; _populatedType = 3; }
#endregion Constructors
#region IEither Implementation
int _populatedType;
object _value;
int IEither.PopulatedType => _populatedType;
object IEither.Value => _value;
Either (int populatedType, object value) => (_populatedType, _value) = (populatedType, value);
#endregion IEither Implementation
#region Value Casts
T1 AsT1 => (T1)_value;
T2 AsT2 => (T2)_value;
T3 AsT3 => (T3)_value;
#endregion Value Casts
#region Switch (method)
public void Switch (Action<T1> ifT1, Action<T2> ifT2, Action<T3> ifT3)
{
switch(_populatedType)
{
case 1: ifT1(AsT1); break;
case 2: ifT2(AsT2); break;
case 3: ifT3(AsT3); break;
default: throw new InvalidOperationException();
}
}
#endregion Switch (method)
#region Nonsimplifying Match
public Either<TResult1, T2, T3> Match<TResult1> (Func<T1, TResult1> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, TResult1> Match<TResult1>
(Func<T1, TResult1> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, TResult2, T3> Match<TResult2> (Func<T2, TResult2> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, TResult2> Match<TResult2>
(Func<T2, TResult2> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
3 => AsT3,
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, TResult3> Match<TResult3> (Func<T3, TResult3> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, TResult3> Match<TResult3>
(Func<T3, TResult3> ifT3, Func<T3, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 when (when (AsT3)) => ifT3 (AsT3),
3 => AsT3,
_ => throw new InvalidOperationException()
};
#endregion Nonsimplifying Match
#region Simplifying Match
public Either<T2, T3> Match
(Func<T1, T2> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T1, T2> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, T3> Match
(Func<T2, T1> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T2, T1> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
3 => AsT3,
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T2, T3> Match
(Func<T1, T3> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T1, T3> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, T2> Match
(Func<T3, T1> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T3, T1> ifT3, Func<T3, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 when (when (AsT3)) => ifT3 (AsT3),
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T3> Match
(Func<T2, T3> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T2, T3> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
3 => AsT3,
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T1, T2> Match
(Func<T3, T2> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T3, T2> ifT3, Func<T3, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 when (when (AsT3)) => ifT3 (AsT3),
3 => AsT3,
_ => throw new InvalidOperationException()
};
#endregion Simplifying Match
#region If (methods)
public bool If (out T1 @if) => If (out @if, out _);
public bool If(out T1 @if, out Either<T2, T3> @else)
{
switch (_populatedType)
{
case 1:
@if = AsT1;
@else = default;
return true;
case 2:
@if = default;
@else = AsT2;
return false;
case 3:
@if = default;
@else = AsT3;
return false;
default:
throw new InvalidOperationException();
}
}
public bool If (out T2 @if) => If (out @if, out _);
public bool If(out T2 @if, out Either<T1, T3> @else)
{
switch (_populatedType)
{
case 1:
@if = default;
@else = AsT1;
return false;
case 2:
@if = AsT2;
@else = default;
return true;
case 3:
@if = default;
@else = AsT3;
return false;
default:
throw new InvalidOperationException();
}
}
public bool If (out T3 @if) => If (out @if, out _);
public bool If(out T3 @if, out Either<T1, T2> @else)
{
switch (_populatedType)
{
case 1:
@if = default;
@else = AsT1;
return false;
case 2:
@if = default;
@else = AsT2;
return false;
case 3:
@if = AsT3;
@else = default;
return true;
default:
throw new InvalidOperationException();
}
}
#endregion If (methods)
#region ToString
public override string ToString() => _populatedType switch
{
1 => typeof (T1).Name + ":" + AsT1,
2 => typeof (T2).Name + ":" + AsT2,
3 => typeof (T3).Name + ":" + AsT3,
_ => throw new InvalidOperationException()
};
#endregion ToString
}
#endregion Either<T1, T2, T3>
#region Either<T1, T2, T3, T4>
public record Either<T1, T2, T3, T4> : IEither
{
#region Implicit Conversion From Value
public static implicit operator Either<T1, T2, T3, T4>(T1 value) => new Either<T1, T2, T3, T4>(value);
public static implicit operator Either<T1, T2, T3, T4>(T2 value) => new Either<T1, T2, T3, T4>(value);
public static implicit operator Either<T1, T2, T3, T4>(T3 value) => new Either<T1, T2, T3, T4>(value);
public static implicit operator Either<T1, T2, T3, T4>(T4 value) => new Either<T1, T2, T3, T4>(value);
#endregion Implicit Conversion From Value
#region Implicit Convertsion By Type Equivalance
public static implicit operator Either<T1, T2, T3, T4> (Either<T1, T2, T4, T3> other)
{
int[] map = new [] { 1, 2, 4, 3 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T1, T3, T2, T4> other)
{
int[] map = new [] { 1, 3, 2, 4 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T1, T3, T4, T2> other)
{
int[] map = new [] { 1, 3, 4, 2 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T1, T4, T2, T3> other)
{
int[] map = new [] { 1, 4, 2, 3 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T1, T4, T3, T2> other)
{
int[] map = new [] { 1, 4, 3, 2 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T2, T1, T3, T4> other)
{
int[] map = new [] { 2, 1, 3, 4 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T2, T1, T4, T3> other)
{
int[] map = new [] { 2, 1, 4, 3 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T2, T3, T1, T4> other)
{
int[] map = new [] { 2, 3, 1, 4 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T2, T3, T4, T1> other)
{
int[] map = new [] { 2, 3, 4, 1 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T2, T4, T1, T3> other)
{
int[] map = new [] { 2, 4, 1, 3 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T2, T4, T3, T1> other)
{
int[] map = new [] { 2, 4, 3, 1 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T3, T1, T2, T4> other)
{
int[] map = new [] { 3, 1, 2, 4 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T3, T1, T4, T2> other)
{
int[] map = new [] { 3, 1, 4, 2 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T3, T2, T1, T4> other)
{
int[] map = new [] { 3, 2, 1, 4 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T3, T2, T4, T1> other)
{
int[] map = new [] { 3, 2, 4, 1 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T3, T4, T1, T2> other)
{
int[] map = new [] { 3, 4, 1, 2 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T3, T4, T2, T1> other)
{
int[] map = new [] { 3, 4, 2, 1 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T4, T1, T2, T3> other)
{
int[] map = new [] { 4, 1, 2, 3 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T4, T1, T3, T2> other)
{
int[] map = new [] { 4, 1, 3, 2 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T4, T2, T1, T3> other)
{
int[] map = new [] { 4, 2, 1, 3 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T4, T2, T3, T1> other)
{
int[] map = new [] { 4, 2, 3, 1 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T4, T3, T1, T2> other)
{
int[] map = new [] { 4, 3, 1, 2 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
public static implicit operator Either<T1, T2, T3, T4> (Either<T4, T3, T2, T1> other)
{
int[] map = new [] { 4, 3, 2, 1 };
return new Either<T1, T2, T3, T4>(map[other._populatedType - 1], other._value);
}
#endregion Implicit Convertsion By Type Equivalance
#region Constructors
public Either (T1 value) { _value = value; _populatedType = 1; }
public Either (T2 value) { _value = value; _populatedType = 2; }
public Either (T3 value) { _value = value; _populatedType = 3; }
public Either (T4 value) { _value = value; _populatedType = 4; }
#endregion Constructors
#region IEither Implementation
int _populatedType;
object _value;
int IEither.PopulatedType => _populatedType;
object IEither.Value => _value;
Either (int populatedType, object value) => (_populatedType, _value) = (populatedType, value);
#endregion IEither Implementation
#region Value Casts
T1 AsT1 => (T1)_value;
T2 AsT2 => (T2)_value;
T3 AsT3 => (T3)_value;
T4 AsT4 => (T4)_value;
#endregion Value Casts
#region Switch (method)
public void Switch (Action<T1> ifT1, Action<T2> ifT2, Action<T3> ifT3, Action<T4> ifT4)
{
switch(_populatedType)
{
case 1: ifT1(AsT1); break;
case 2: ifT2(AsT2); break;
case 3: ifT3(AsT3); break;
case 4: ifT4(AsT4); break;
default: throw new InvalidOperationException();
}
}
#endregion Switch (method)
#region Nonsimplifying Match
public Either<TResult1, T2, T3, T4> Match<TResult1> (Func<T1, TResult1> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, TResult2, T3, T4> Match<TResult2> (Func<T2, TResult2> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, TResult3, T4> Match<TResult3> (Func<T3, TResult3> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, TResult4> Match<TResult4> (Func<T4, TResult4> ifT4) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 => ifT4 (AsT4),
_ => throw new InvalidOperationException()
};
#endregion Nonsimplifying Match
#region Simplifying Match
public Either<T2, T3, T4> Match
(Func<T1, T2> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T1, T2> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, T3, T4> Match
(Func<T2, T1> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T2, T1> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T2, T3, T4> Match
(Func<T1, T3> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T1, T3> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T4> Match
(Func<T3, T1> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T3, T1> ifT3, Func<T3, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 when (when (AsT3)) => ifT3 (AsT3),
4 => AsT4,
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T2, T3, T4> Match
(Func<T1, T4> ifT1) => _populatedType switch
{
1 => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T1, T4> ifT1, Func<T1, bool> when) => _populatedType switch
{
1 when (when (AsT1)) => ifT1 (AsT1),
2 => AsT2,
3 => AsT3,
4 => AsT4,
1 => AsT1,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T4, T1> ifT4) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 => ifT4 (AsT4),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T4, T1> ifT4, Func<T4, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 when (when (AsT4)) => ifT4 (AsT4),
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T3, T4> Match
(Func<T2, T3> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T2, T3> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T4> Match
(Func<T3, T2> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T3, T2> ifT3, Func<T3, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 when (when (AsT3)) => ifT3 (AsT3),
4 => AsT4,
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T3, T4> Match
(Func<T2, T4> ifT2) => _populatedType switch
{
1 => AsT1,
2 => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T2, T4> ifT2, Func<T2, bool> when) => _populatedType switch
{
1 => AsT1,
2 when (when (AsT2)) => ifT2 (AsT2),
3 => AsT3,
4 => AsT4,
2 => AsT2,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T4, T2> ifT4) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 => ifT4 (AsT4),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T4, T2> ifT4, Func<T4, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 when (when (AsT4)) => ifT4 (AsT4),
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T4> Match
(Func<T3, T4> ifT3) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => ifT3 (AsT3),
4 => AsT4,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T3, T4> ifT3, Func<T3, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 when (when (AsT3)) => ifT3 (AsT3),
4 => AsT4,
3 => AsT3,
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3> Match
(Func<T4, T3> ifT4) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 => ifT4 (AsT4),
_ => throw new InvalidOperationException()
};
public Either<T1, T2, T3, T4> Match
(Func<T4, T3> ifT4, Func<T4, bool> when) => _populatedType switch
{
1 => AsT1,
2 => AsT2,
3 => AsT3,
4 when (when (AsT4)) => ifT4 (AsT4),
4 => AsT4,
_ => throw new InvalidOperationException()
};
#endregion Simplifying Match
#region If (methods)
public bool If (out T1 @if) => If (out @if, out _);
public bool If(out T1 @if, out Either<T2, T3, T4> @else)
{
switch (_populatedType)
{
case 1:
@if = AsT1;
@else = default;
return true;
case 2:
@if = default;
@else = AsT2;
return false;
case 3:
@if = default;
@else = AsT3;
return false;
case 4:
@if = default;
@else = AsT4;
return false;
default:
throw new InvalidOperationException();
}
}
public bool If (out T2 @if) => If (out @if, out _);
public bool If(out T2 @if, out Either<T1, T3, T4> @else)
{
switch (_populatedType)
{
case 1:
@if = default;
@else = AsT1;
return false;
case 2:
@if = AsT2;
@else = default;
return true;
case 3:
@if = default;
@else = AsT3;
return false;
case 4:
@if = default;
@else = AsT4;
return false;
default:
throw new InvalidOperationException();
}
}
public bool If (out T3 @if) => If (out @if, out _);
public bool If(out T3 @if, out Either<T1, T2, T4> @else)
{
switch (_populatedType)
{
case 1:
@if = default;
@else = AsT1;
return false;
case 2:
@if = default;
@else = AsT2;
return false;
case 3:
@if = AsT3;
@else = default;
return true;
case 4:
@if = default;
@else = AsT4;
return false;
default:
throw new InvalidOperationException();
}
}
public bool If (out T4 @if) => If (out @if, out _);
public bool If(out T4 @if, out Either<T1, T2, T3> @else)
{
switch (_populatedType)
{
case 1:
@if = default;
@else = AsT1;
return false;
case 2:
@if = default;
@else = AsT2;
return false;
case 3:
@if = default;
@else = AsT3;
return false;
case 4:
@if = AsT4;
@else = default;
return true;
default:
throw new InvalidOperationException();
}
}
#endregion If (methods)
#region ToString
public override string ToString() => _populatedType switch
{
1 => typeof (T1).Name + ":" + AsT1,
2 => typeof (T2).Name + ":" + AsT2,
3 => typeof (T3).Name + ":" + AsT3,
4 => typeof (T4).Name + ":" + AsT4,
_ => throw new InvalidOperationException()
};
#endregion ToString
}
#endregion Either<T1, T2, T3, T4>
void Main()
{
var maxEitherSize = 4;
EitherBuilder
.GetFactory(maxEitherSize)()
.ToString()
.Dump();
}
public static class EitherBuilder
{
private const int tabWidth = 4;
private static Func<int, string> Indent = Utils.Memoize((int countIndents) => new String(' ', tabWidth * countIndents));
private static Func<IEnumerable<int>, string> GetTypeList = Utils.Memoize((IEnumerable<int> orderedMembers) => orderedMembers.ToStringJoin(", T", "T"));
private static Func<int, string> GetClassTypeList = (int numMembers) => GetTypeList(EnumerateMembers(numMembers));
private static Func<int, IEnumerable<int>> EnumerateMembers = (int numMembers) => Enumerable.Range(1, numMembers);
private static IEnumerable<T> SelectMembers<T>(int numMembers, Func<int, T> map) => EnumerateMembers(numMembers).Select(map);
private static IEnumerable<int> MembersExcept(int numMembers, int excluded) => EnumerateMembers(numMembers).Where(x => x != excluded);
private static IEnumerable<T> SelectMembersWhere<T>(int numMembers, Func<int, bool> predicate, Func<int, T> map) => EnumerateMembers(numMembers).Where(predicate).Select(map);
private static IEnumerable<T> SelectManyMembers<T>(int numMembers, Func<int, IEnumerable<T>> map) => EnumerateMembers(numMembers).SelectMany(map);
private static string throwOnOutOfBounds => "throw new InvalidOperationException()";
#region String Builder Utility Extensions
private static IDisposable AppendRegionTag(this StringBuilder doc, string name = "", int indentLevel = 1)
{
if (name is null) return Disposable.Create(() => { });
doc.AppendLine($"{Indent(indentLevel)}#region {name}");
return Disposable.Create(() => doc.AppendLine($"{Indent(indentLevel)}#endregion {name}"));
}
// not really a string, but i need sleep soon, so close enough
private static StringBuilder AppendLines(this StringBuilder builder, IEnumerable<string> lines, string regionName = null, int regionIndentLevel = 1, bool addSeperator = true)
{
using (var region = builder.AppendRegionTag(regionName, regionIndentLevel))
{
foreach (var line in lines)
{
builder.AppendLine(line);
}
}
if (addSeperator)
{
builder.AppendLine("");
}
return builder;
}
#endregion String Builder Utility Extensions
#region FactoryMethod
public static Func<StringBuilder> GetFactory(int maxNumTypeMembers) => () =>
{
var doc = new StringBuilder();
doc = EitherBuilder.InterfaceFactory(doc);
foreach (var members in Enumerable.Range(2, maxNumTypeMembers - 1))
{
doc = EitherBuilder.GetFactoryNTypes(members, maxNumTypeMembers)(doc);
}
return doc;
};
private static Func<StringBuilder, StringBuilder> GetFactoryNTypes(int numTypeMemebers, int maxNumTypeMembers) => (StringBuilder doc) =>
{
doc
.AppendLines(EitherBuilder.ClassHeader(numTypeMemebers), addSeperator: false)
.AppendLines(EitherBuilder.ImplicitCreation(numTypeMemebers), "Implicit Conversion From Value")
.AppendLines(EitherBuilder.ImplicitConversion(numTypeMemebers), "Implicit Convertsion By Type Equivalance")
.AppendLines(EitherBuilder.Constructors(numTypeMemebers), "Constructors")
.AppendLines(EitherBuilder.IEitherImpl(numTypeMemebers), "IEither Implementation")
.AppendLines(EitherBuilder.ValueCasts(numTypeMemebers), "Value Casts")
.AppendLines(EitherBuilder.SwitchMethod(numTypeMemebers), "Switch (method)")
.AppendLines(EitherBuilder.MatchNonSimplifyingMethods(numTypeMemebers, maxNumTypeMembers), "Nonsimplifying Match")
.AppendLines(EitherBuilder.MatchSimplifyingMethods(numTypeMemebers), "Simplifying Match")
.AppendLines(EitherBuilder.IfMethods(numTypeMemebers), "If (methods)")
.AppendLines(EitherBuilder.ToStringMethod(numTypeMemebers), "ToString", addSeperator: false)
.AppendLines(EitherBuilder.ClassFooter(numTypeMemebers));
doc.AppendLine();
return doc;
};
#endregion FactoryMethod
#region parts
private static Func<StringBuilder, StringBuilder> InterfaceFactory = (StringBuilder doc) =>
{
var interfaceDef = new[] {
$"public interface IEither",
"{",
$"{Indent(1)}int PopulatedType {{ get; }}",
$"{Indent(1)}object Value {{ get; }}",
"}",
};
return doc.AppendLines(interfaceDef, "IEither interface", 0);
};
private static Func<int, IEnumerable<string>> ClassHeader = (int numMembers) => new[] {
$"#region Either<{GetClassTypeList(numMembers)}>",
$"public record Either<{GetClassTypeList(numMembers)}> : IEither",
"{"
};
private static Func<int, IEnumerable<string>> ClassFooter = (int numMembers) => new [] {
"}",
$"#endregion Either<{GetClassTypeList(numMembers)}>",
};
private static Func<int, IEnumerable<string>> ImplicitCreation = (int members) => Enumerable.Range(1, members).Select(x => $"{Indent(1)}public static implicit operator Either<{GetClassTypeList(members)}>(T{x} value) => new Either<{GetClassTypeList(members)}>(value);");
private static Func<int, IEnumerable<string>> ImplicitConversion = (int members) => EnumerateMembers(members).ToArray()
.GetPermutations()
.Where(x => !x.All((y, i) => y == (i + 1))) // exclude the set for the current class
.SelectMany(
anOrderedSet => new[] {$"{Indent(1)}public static implicit operator Either<{GetClassTypeList(members)}> (Either<{GetTypeList(anOrderedSet)}> other)",
$"{Indent(1)}{{",
$"{Indent(2)}int[] map = new [] {{ {anOrderedSet.ToStringJoin(", ")} }};",
$"{Indent(2)}return new Either<{GetClassTypeList(members)}>(map[other._populatedType - 1], other._value);",
$"{Indent(1)}}}"
});
private static Func<int, IEnumerable<string>> Constructors = (int members) => Enumerable.Range(1, members)
.Select(x => $"{Indent(1)}public Either (T{x} value) {{ _value = value; _populatedType = {x}; }}");
private static Func<int, IEnumerable<string>> ValueCasts = (int members) => Enumerable.Range(1, members)
.Select(x => $"{Indent(1)}T{x} AsT{x} => (T{x})_value;");
#region IEither implementation
private static Func<int, IEnumerable<string>> IEitherImpl = (int members) => new[] {
$"{Indent(1)}int _populatedType;",
$"{Indent(1)}object _value;",
$"",
$"{Indent(1)}int IEither.PopulatedType => _populatedType;",
$"{Indent(1)}object IEither.Value => _value;",
$"",
$"{Indent(1)}Either (int populatedType, object value) => (_populatedType, _value) = (populatedType, value);",
};
#endregion IEither implementation";
private static Func<int, IEnumerable<string>> SwitchMethod = (int numMembers) => new [] {
new[] {
$"{Indent(1)}public void Switch ({SelectMembers(numMembers, x => $"Action<T{x}> ifT{x}").Join(", ")})",
$"{Indent(1)}{{",
$"{Indent(2)}switch(_populatedType)",
$"{Indent(2)}{{",
}, SelectMembers(numMembers, x => $"{Indent(3)}case {x}: ifT{x}(AsT{x}); break;")
.ToArray(),
new[] {
$"{Indent(3)}default: {throwOnOutOfBounds};",
$"{Indent(2)}}}",
$"{Indent(1)}}}"
}
}
.SelectMany(x=>x)
.ToArray();
#region MatchNonSimplifying
private static Func<int, int, IEnumerable<string>> MatchNonSimplifyingMethods = (int members, int maxEitherSize) => SelectManyMembers(members, x =>
new [] {
new [] {
$"{Indent(1)}public Either<{SelectMembers(members, y => y == x ? $"TResult{x}": $"T{y}").Join(", ")}> Match<TResult{x}> (Func<T{x}, TResult{x}> ifT{x}) => _populatedType switch",
$"{Indent(1)}{{",
},
SelectMembers(members, y => $"{Indent(2)}{(y == x ? $"{x} => ifT{x} (AsT{x})," : $"{y} => AsT{y},")}").ToArray(),
new [] {
$"{Indent(2)}_ => {throwOnOutOfBounds}",
$"{Indent(1)}}};",
"",
},
// if statement non simplifying
members < maxEitherSize ?
new [] {
$"{Indent(1)}public Either<{SelectMembers(members, y => $"T{y}").Append($"TResult{x}").Join(", ")}> Match<TResult{x}>",
$"{Indent(2)}(Func<T{x}, TResult{x}> ifT{x}, Func<T{x}, bool> when) => _populatedType switch",
$"{Indent(1)}{{",
} : new string[] {},
members < maxEitherSize ?SelectMembers(members, y => $"{Indent(2)}{(y == x ? $"{x} when (when (AsT{y})) => ifT{y} (AsT{y})," : $"{y} => AsT{y},")}")
.Append($"{Indent(2)}{x} => AsT{x},")
.ToArray(): new string[] {},
members < maxEitherSize ?new [] {
$"{Indent(2)}_ => {throwOnOutOfBounds}",
$"{Indent(1)}}};",
}: new string[] {},
}.SelectMany(x => x)
);
#endregion MatchNonSimplifying
#region MatchSimplifying
private static Func<int, IEnumerable<string>> MatchSimplifyingMethods = (int members) => EnumerateMembers(members)
.ToArray()
.GetPermutationsOfPairs()
.SelectMany(vector =>
new[] {
new [] {
$"{Indent(1)}public {(members > 2 ? $"Either<{SelectMembersWhere(members, x => x != vector[0], x => $"T{x}").Join(", ")}>": $"T{MembersExcept(members, vector[0]).Single()}" )} Match",
$"{Indent(2)}(Func<T{vector[0]}, T{vector[1]}> ifT{vector[0]}) => _populatedType switch",
$"{Indent(1)}{{",
},
SelectMembers(members, y => $"{Indent(2)}{(y == vector[0] ? $"{vector[0]} => ifT{vector[0]} (AsT{vector[0]})," : $"{y} => AsT{y},")}")
.ToArray(),
new [] {
$"{Indent(2)}_ => {throwOnOutOfBounds}",
$"{Indent(1)}}};",
},
// when statement simplifying
new [] {
$"{Indent(1)}public Either<{SelectMembers(members, x => $"T{x}").Join(", ")}> Match",
$"{Indent(2)}(Func<T{vector[0]}, T{vector[1]}> ifT{vector[0]}, Func<T{vector[0]}, bool> when) => _populatedType switch",
$"{Indent(1)}{{",
},
SelectMembers(members, y => $"{Indent(2)}{(y == vector[0] ? $"{vector[0]} when (when (AsT{vector[0]})) => ifT{vector[0]} (AsT{vector[0]})," : $"{y} => AsT{y},")}")
.Append($"{Indent(2)}{vector[0]} => AsT{vector[0]},")
.ToArray(),
new [] {
$"{Indent(2)}_ => {throwOnOutOfBounds}",
$"{Indent(1)}}};",
},
}.SelectMany(x => x)
);
#endregion MatchSimplifying
#region If
private static Func<int, IEnumerable<string>> IfMethods = (int members) => SelectManyMembers(members, x =>
new[] {
new [] {
$"{Indent(1)}public bool If (out T{x} @if) => If (out @if, out _);",
$"{Indent(1)}public bool If(out T{x} @if, out {(members > 2 ? $"Either<{SelectMembersWhere(members, y => y != x, y => $"T{y}").Join(", ")}>": $"T{MembersExcept(members, x).Single()}")} @else)",
$"{Indent(1)}{{",
$"{Indent(2)}switch (_populatedType)",
$"{Indent(2)}{{",
},
SelectManyMembers(members, y => new [] {
$"{Indent(3)}case {y}:",
$"{Indent(4)}@if = {(y == x ? $"AsT{x}" : "default")};",
$"{Indent(4)}@else = {(y == x ? "default" : $"AsT{y}")};",
$"{Indent(4)}return {(y == x ? "true" : "false" )};",
}), new [] {
$"{Indent(3)}default:",
$"{Indent(4)}{throwOnOutOfBounds};",
$"{Indent(2)}}}",
$"{Indent(1)}}}",
}
}.SelectMany(x=>x)
);
#endregion If
#region ToString
private static Func<int, IEnumerable<string>> ToStringMethod = (int members) => new [] {
new [] {
$"{Indent(1)}public override string ToString() => _populatedType switch",
$"{Indent(1)}{{",
},
SelectMembers(members, x => $"{Indent(2)}{x} => typeof (T{x}).Name + \":\" + AsT{x},").ToArray(),
new [] {
$"{Indent(2)}_ => {throwOnOutOfBounds}",
$"{Indent(1)}}};",
}
}.SelectMany(x => x);
#endregion ToString
#endregion parts
}
public static class Utils
{
public static Func<TIn, TOut> Memoize<TIn, TOut>(Func<TIn, TOut> func)
{
var cache = new Dictionary<TIn, TOut>();
return input =>
cache.TryGetValue(input, out var result)
? result
: cache[input] = func(input);
}
}
static class SetExtensions
{
// not efficient, but neat. if someone want to have 500 types in the either, they have bigger issues
public static IEnumerable<T[]> GetPermutations<T>(this T[] elements)
{
if (elements.Length == 1)
{
yield return elements;
yield break;
}
for (int i = 0; i < elements.Length; i++)
{
var element = elements[i];
var permOfOthers = elements.Where((x, idx) => idx != i).ToArray().GetPermutations();
foreach (var partialPermutation in permOfOthers)
{
yield return partialPermutation.Prepend(element).ToArray();
}
}
}
/// <summary>for a given set of items, gets each distinct pairing of items.</summary>
/// <param name="orderSensitive" >flag to define if the order of the result pair matters. defaults to true.</param>
public static IEnumerable<T[]> GetPermutationsOfPairs<T>(this T[] elements, bool orderSensitive = true)
{
if (elements.Length <= 1)
{
throw new ArgumentException(nameof(elements));
}
for (int i = 0; i < (elements.Length - 1); i++)
{
var paired = i + 1;
while (paired < elements.Length)
{
yield return new[] { elements[i], elements[paired] };
if (orderSensitive)
{
yield return new[] { elements[paired], elements[i] };
}
paired++;
}
}
}
}
static class StringExtensions
{
public static string ToStringJoin<T>(this IEnumerable<T> futureStrings, string seperator, string prepend = null)
{
return futureStrings.Select(x=>x.ToString()).Join(seperator, prepend);
}
public static string Join(this IEnumerable<string> strings, string seperator, string prepend = null)
{
var result = string.Join(seperator, strings);
return !string.IsNullOrWhiteSpace(prepend)
? prepend + result
: result;
}
public static string Join(this IEnumerable<string> strings, char seperator) // this would be great to have either for
{
return string.Join(seperator, strings);
}
}
public class Disposable : IDisposable
{
private Action _onDispose;
private Disposable(Action onDispose) {
_onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
}
#region static access
/// <summary>allows creates of a short lived disposable</summary>
public static Disposable Create(Action dispose) {
return new Disposable(dispose);
}
#endregion
#region IDisposable Impl
private bool _disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose() => Dispose(true);
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// Dispose managed state (managed objects).
_onDispose();
}
_disposed = true;
}
#endregion IDisposable Impl
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment