All Downloads are FREE. Search and download functionalities are using the official Maven repository.

runtime.csharp.IRT.Either.cs Maven / Gradle / Ivy

The newest version!

using System;
// TODO Consider moving out of here the converter attribute
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using IRT.Marshaller;

namespace IRT {
    [JsonConverter(typeof(Either_JsonNetConverter))]
    public abstract class Either {
        public abstract Either Map(Func f);
        public abstract Either BiMap(Func g, Func f);
        public abstract B Fold(Func whenRight, Func whenLeft);
        public abstract L GetOrElse(L l);
        public abstract L GetLeft();
        public abstract A GetOrElse(A a);
        public abstract A GetRight();
        public abstract bool IsLeft();
        public abstract bool IsRight();
        public abstract Either Swap();
        public abstract Either FilterOrElse(Func p, L zero);
        public abstract void Match(Action whenRight, Action whenLeft);

        public static explicit operator L(Either e) {
            if (!e.IsLeft()) {
                throw new InvalidCastException("Either is not in the Left state.");
            }

            return e.GetLeft();
        }

        public static explicit operator A(Either e) {
            if (e.IsLeft()) {
                throw new InvalidCastException("Either is not in the Right state.");
            }

            return e.GetRight();
        }

        // We support both sides to have equal type, for example if both
        // success and failure hold a simple Message class. In that case
        // same operator would fail, so we move it to the generated class.
        public static implicit operator Either (L value) {
            return new Left(value);
        }

        public static implicit operator Either (A value) {
            return new Right(value);
        }

        public sealed class Left: Either {
            private readonly L Value;
            public Left(L value) {
                Value = value;
            }

            public override Either Map(Func f) {
                return new Either.Left(Value);
            }

            public override Either BiMap(Func g, Func f) {
                return new Either.Left(f(Value));
            }

            public override B Fold(Func whenRight, Func whenLeft) {
                return whenLeft(Value);
            }

            public override L GetOrElse(L l) {
                return Value;
            }

            public override A GetOrElse(A a) {
                return a;
            }

            public override L GetLeft() {
                return Value;
            }

            public override A GetRight() {
                throw new InvalidCastException("Either is not in the Right state.");
            }

            public override bool IsLeft() {
                return true;
            }

            public override bool IsRight() {
                return false;
            }

            public override Either Swap() {
                return new Either.Right(Value);
            }

            public override Either FilterOrElse(Func p, L zero) {
                return this;
            }

            public override void Match(Action whenRight, Action whenLeft) {
                whenLeft(Value);
            }
        }

        public sealed class Right: Either {
            private readonly A Value;
            public Right(A value) {
                Value = value;
            }

            public override Either Map(Func f) {
                return new Either.Right(f(Value));
            }

            public override Either BiMap(Func g, Func f) {
                return new Either.Right(g(Value));
            }

            public override B Fold(Func whenRight, Func whenLeft) {
                return whenRight(Value);
            }

            public override L GetOrElse(L l) {
                return l;
            }

            public override A GetOrElse(A a) {
                return Value;
            }

            public override L GetLeft() {
                throw new InvalidCastException("Either is not in the Left state.");
            }

            public override A GetRight() {
                return Value;
            }

            public override bool IsLeft() {
                return false;
            }

            public override bool IsRight() {
                return true;
            }

            public override Either Swap() {
                return new Either.Left(Value);
            }

            public override Either FilterOrElse(Func p, L zero) {
                if (p(Value)) {
                    return this;
                }

                return new Left(zero);
            }

            public override void Match(Action whenRight, Action whenLeft) {
                whenRight(Value);
            }
        }
    }

    // TODO Consider moving out of here to the marshaller itself
    // This implementation seems to trigger some issues with Json.net, as it doesn't know how to
    // deserialize a generic type like this...
//     public class Either_JsonNetConverter: JsonNetConverter> {
//         public override void WriteJson(JsonWriter writer, Either al, JsonSerializer serializer) {
//             writer.WriteStartObject();

//             if (al.IsLeft()) {
//                 writer.WritePropertyName("Failure");
//                 var l = al.GetLeft();
//                 if (typeof(L).IsInterface) {
//                     // Serializing polymorphic type
//                     writer.WriteStartObject();
//                     writer.WritePropertyName((l as IRTTI).GetFullClassName());
//                     serializer.Serialize(writer, l);
//                     writer.WriteEndObject();
//                 } else {
//                     serializer.Serialize(writer, l);
//                 }
//             } else {
//                 writer.WritePropertyName("Success");
//                 var r = al.GetRight();
//                 if (typeof(R).IsInterface) {
//                     // Serializing polymorphic type
//                     writer.WriteStartObject();
//                     writer.WritePropertyName((r as IRTTI).GetFullClassName());
//                     serializer.Serialize(writer, r);
//                     writer.WriteEndObject();
//                 } else {
//                     serializer.Serialize(writer, r);
//                 }
//             }

//             writer.WriteEndObject();
//         }

//         public override Either ReadJson(JsonReader reader, System.Type objectType, Either existingValue, bool hasExistingValue, JsonSerializer serializer) {
//             var json = JObject.Load(reader);
//             var kv = json.Properties().First();
//             switch (kv.Name) {
//                 case "Success": {
//                     var v = serializer.Deserialize(kv.Value.CreateReader());
//                     return new Either.Right(v);
//                 }

//                 case "Failure": {
//                     var v = serializer.Deserialize(kv.Value.CreateReader());
//                     return new Either.Left(v);
//                 }

//                 default:
//                     throw new System.Exception("Unknown either Either type: " + kv.Name);
//             }
//         }
//     }
    
    public class Either_JsonNetConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Type valueType = value.GetType();
            Type[] genArgs = valueType.GetGenericArguments();
            
            writer.WriteStartObject();

            if ((bool)valueType.GetMethod("IsLeft").Invoke(value, new object[0])) {
                writer.WritePropertyName("Failure");
                var l = valueType.GetMethod("GetLeft").Invoke(value, new object[0]);
                if (genArgs[0].IsInterface) {
                    // Serializing polymorphic type
                    writer.WriteStartObject();
                    writer.WritePropertyName((l as IRTTI).GetFullClassName());
                    serializer.Serialize(writer, l);
                    writer.WriteEndObject();
                } else {
                    serializer.Serialize(writer, l);
                }
            } else {
                writer.WritePropertyName("Success");
                var r = valueType.GetMethod("GetRight").Invoke(value, new object[0]);
                if (genArgs[1].IsInterface) {
                    // Serializing polymorphic type
                    writer.WriteStartObject();
                    writer.WritePropertyName((r as IRTTI).GetFullClassName());
                    serializer.Serialize(writer, r);
                    writer.WriteEndObject();
                } else {
                    serializer.Serialize(writer, r);
                }
            }

            writer.WriteEndObject();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            Type[] genArgs = objectType.GetGenericArguments();
            
            var json = JObject.Load(reader);
            var kv = json.Properties().First();
            switch (kv.Name) {
                case "Success": {
                    var v = serializer.Deserialize(kv.Value.CreateReader(), genArgs[1]);
                    var rightType = typeof(Either<,>.Right).MakeGenericType(genArgs);

                    return Activator.CreateInstance(rightType, v);
                }

                case "Failure": {
                    var v = serializer.Deserialize(kv.Value.CreateReader(), genArgs[0]);
                    var leftType = typeof(Either<,>.Left).MakeGenericType(genArgs);

                    return Activator.CreateInstance(leftType, v);
                }

                default:
                    throw new System.Exception("Unknown either Either type: " + kv.Name);
            }
        }

        public override bool CanConvert(Type objectType)
        {
            return typeof(Either<,>).IsAssignableFrom(objectType);
        }
    }
}