I have a type called Omissible<T> that acts like your regular option type, except that it's explicitly distinct from and orthogonal to T's nullability. It expresses "specified-ness" rather than "emptiness". It looks like this:
/// <summary>
/// Represents a value that can be left unspecified, as explicitly distinct from <c>null</c> in that it carries the semantics of "unset" as opposed to "set to 'empty' (i.e. null)", and thereby disambiguates the two, making nullability and optionality orthogonal, or put another way, making nullability (in the loose sense) two-dimensional for <typeparamref name="T"/>.
/// An Omissible in an unset state means "this field/parameter/property has been left out (whatever the context may be) and ought to be omitted from consideration".
/// </summary>
/// <remarks>
/// Enables patch (maximally surgical) updates end-to-end — this will be reflected also in the event store entries.
/// Properties of this type should be omitted from serialized payloads of their containing objects if they are in the "unset" state.
/// </remarks>
[JsonConverter(typeof(OmissibleJsonConverter))]
[BsonSerializer(typeof(OmissibleBsonSerializer<>))]
public readonly record struct Omissible<T>(T? Value) : IOmissible, IEquatable<Omissible<T>>, IShapeable<Omissible<T>>
{
public bool IsSet { get; } = true;
public static implicit operator Omissible<T>(UnsetMarker _) =>
default; // NOTE: `default` results in a `{ Value = default; IsSet = false; }` struct which represents an unset state. (Also, don't be fooled by the `= true` initializer of `IsSet`, that won't take effect when using `default` — see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/structs#:~:text=The%20default%20value%20of%20a%20struct%20is%20the%20value%20produced%20by%20setting%20all%20fields%20to%20their%20default%20value)
public static implicit operator Omissible<T>(T? value) =>
new(value);
public static Omissible<T> Unset { get; } = default;
public override string? ToString() =>
IsSet
? Value?.ToString()
: "UNSET";
// NOTE: We intentionally override the default record type `Equals` (and by extension `GetHashCode`) implementation because `default(SomeVogenType) != default(SomeVogenType)`, interestingly enough, and what that means is that we don't to invoke `T`'s `Equals` if both instances are unset (i.e. `IsSet == false` for both).
public bool Equals(Omissible<T> other) =>
IsSet
? other.IsSet && EqualityComparer<T>.Default.Equals(Value, other.Value)
: !other.IsSet;
public override int GetHashCode() =>
IsSet
? Value is null
? 0 // NOTE: `null` is the idiomatic hash code for null — see https://stackoverflow.com/q/10723458
: EqualityComparer<T>.Default.GetHashCode(Value)
: -1; // NOTE: We return `-1` for unset to distinguish it from `null`.
}
As you can tell, its semantics map quite cleanly to PolyType's IOptionalTypeShape<TOptional, TElement>, but I can't seem to figure out how I should, in fact, code the "adapter" layer here; having read the documentation + searched through the issues in this repo.
I tried to have Omissible<T> implement ITypeShape<Omissible<T>>, and then implement a separate custom class that implements IOptionalTypeShape<Omissible<T>, T>>, but I got this error:
The type 'global::Avesta.Common.OmissibleTypeShape' implements 'global::PolyType.Abstractions.IOptionalTypeShape<global::Avesta.Common.Omissible, T>', which is a reserved interface that should only be implemented by PolyType itself.
So, apparently this is not the way to go.
Is this kind of thing supported in PolyType at all? Or am I missing something?
I have a type called
Omissible<T>that acts like your regular option type, except that it's explicitly distinct from and orthogonal toT's nullability. It expresses "specified-ness" rather than "emptiness". It looks like this:As you can tell, its semantics map quite cleanly to PolyType's
IOptionalTypeShape<TOptional, TElement>, but I can't seem to figure out how I should, in fact, code the "adapter" layer here; having read the documentation + searched through the issues in this repo.I tried to have
Omissible<T>implementITypeShape<Omissible<T>>, and then implement a separate custom class that implementsIOptionalTypeShape<Omissible<T>, T>>, but I got this error:So, apparently this is not the way to go.
Is this kind of thing supported in PolyType at all? Or am I missing something?