All Versions
37
Latest Version
Avg Release Cycle
20 days
Latest Release
346 days ago

Changelog History
Page 2

  • v3.3.41 Changes

    November 28, 2019

    ๐Ÿš€ This is the first-pass release of the LanguageExt.CodeGen feature for generating record types. This means there's no need to derive from Record<TYPE> any more, and also allows records to be structs, which is a real bonus.

    To create a new record, simply attach the [Record] attribute to a partial class or a partial struct:

     [Record] public partial struct Person { public readonly string Forename; public readonly string Surname; }
    

    You may also use properties:

     [Record] public partial struct Person { public string Forename { get; } public string Surname { get; } }
    

    As well as computed properties:

     [Record] public partial struct Person { public string Forename { get; } public string Surname { get; } public string FullName =\> $"{Forename} {Surname}"; }
    

    The features of the generated record are:

    • Auto constructor provision
    • Auto deconstructor provision
    • Structural equality (with equality operators also)
    • Structural ordering (with ordering operators also)
    • GetHashCode provision
    • Serialisation
    • 0๏ธโƒฃ Sensible default ToString implementation
    • With method for immutable transformation
    • Lens fields for composing nested immutable type transformation

    0๏ธโƒฃ Coming soon (for both records and unions) is the ability to provide class-instances to override the default behaviour of equality, ordering, hash-code generation, and constructor validation.

    The generated code looks like this:

     [System.Serializable] public partial struct Person : System.IEquatable\<Person\>, System.IComparable\<Person\>, System.IComparable, System.Runtime.Serialization.ISerializable { public Person(string Forename, string Surname) { this.Forename = Forename; this.Surname = Surname; } public void Deconstruct(out string Forename, out string Surname) { Forename = this.Forename; Surname = this.Surname; } public Person(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { this.Forename = (string)info.GetValue("Forename", typeof(string)); this.Surname = (string)info.GetValue("Surname", typeof(string)); } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("Forename", this.Forename); info.AddValue("Surname", this.Surname); } public static bool operator ==(Person x, Person y) =\> x.Equals(y); public static bool operator !=(Person x, Person y) =\> !(x == y); public static bool operator\>(Person x, Person y) =\> x.CompareTo(y) \> 0; public static bool operator \<(Person x, Person y) =\> x.CompareTo(y) \< 0; public static bool operator \>=(Person x, Person y) =\> x.CompareTo(y) \>= 0; public static bool operator \<=(Person x, Person y) =\> x.CompareTo(y) \<= 0; public bool Equals(Person other) { if (LanguageExt.Prelude.isnull(other)) return false; if (!default(LanguageExt.ClassInstances.EqDefault\<string\>).Equals(this.Forename, other.Forename)) return false; if (!default(LanguageExt.ClassInstances.EqDefault\<string\>).Equals(this.Surname, other.Surname)) return false; return true; } public override bool Equals(object obj) =\> obj is Person tobj && Equals(tobj); public int CompareTo(object obj) =\> obj is Person p ? CompareTo(p) : 1; public int CompareTo(Person other) { if (LanguageExt.Prelude.isnull(other)) return 1; int cmp = 0; cmp = default(LanguageExt.ClassInstances.OrdDefault\<string\>).Compare(this.Forename, other.Forename); if (cmp != 0) return cmp; cmp = default(LanguageExt.ClassInstances.OrdDefault\<string\>).Compare(this.Surname, other.Surname); if (cmp != 0) return cmp; return 0; } public override int GetHashCode() { const int fnvOffsetBasis = -2128831035; const int fnvPrime = 16777619; int state = fnvOffsetBasis; unchecked { state = (default(LanguageExt.ClassInstances.EqDefault\<string\>).GetHashCode(this.Forename) ^ state) \* fnvPrime; state = (default(LanguageExt.ClassInstances.EqDefault\<string\>).GetHashCode(this.Surname) ^ state) \* fnvPrime; } return state; } public override string ToString() { var sb = new System.Text.StringBuilder(); sb.Append("Person("); sb.Append(LanguageExt.Prelude.isnull(Forename) ? $"Forename: [null]" : $"Forename: {Forename}"); sb.Append($", "); sb.Append(LanguageExt.Prelude.isnull(Surname) ? $"Surname: [null]" : $"Surname: {Surname}"); sb.Append(")"); return sb.ToString(); } public Person With(string Forename = null, string Surname = null) =\> new Person(Forename ?? this.Forename, Surname ?? this.Surname); public static readonly Lens\<Person, string\> forename = Lens\<Person, string\>.New(\_x =\> \_x.Forename, \_x =\> \_y =\> \_y.With(Forename: \_x)); public static readonly Lens\<Person, string\> surname = Lens\<Person, string\>.New(\_x =\> \_x.Surname, \_x =\> \_y =\> \_y.With(Surname: \_x)); }
    
  • v3.3.34 Changes

    November 10, 2019

    ๐Ÿš€ Continuing from the two releases [1],[2] this weekend relating to the new discriminated-union feature of language-ext...

    ๐Ÿ‘ There is now support for creating unions from abstract classes. Although this is slightly less terse than using interfaces, there is a major benefit: classes can contain operators and so the equality and ordering operators can be automatically generated.

    So, as well as being able to create unions from interfaces like so:

     [Union] public interface Shape { Shape Rectangle(float width, float length); Shape Circle(float radius); Shape Prism(float width, float height); }
    

    You can now additionally create them from an abstract partial class like so:

     [Union] public abstract partial class Shape { public abstract Shape Rectangle(float width, float length); public abstract Shape Circle(float radius); public abstract Shape Prism(float width, float height); }
    

    Which allows for:

    Shape shape1 = ShapeCon.Circle(100); Shape shape2 = ShapeCon.Circle(100); Shape shape3 = ShapeCon.Circle(50); Assert.True(shape1 == shape2); Assert.False(shape2 == shape3); Assert.True(shape2 \> shape3); 
    

    Case classes are now sealed rather than partial. partial opens the door to addition of fields and properties which could compromise the case-type. And so extension methods are the best way of adding functionality to the case-types.

    ๐Ÿšš To make all of this work with abstract classes I needed to remove the inheritance of Record<CASE_TYPE> from each union case, and so now the generated code does the work of the Record type at compile-time rather than at run time. It's lead to a slight explosion in the amount of generated code, but I guess it shows how hard it is to do this manually!

     [System.Serializable] public sealed class Rectangle : \_ShapeBase, System.IEquatable\<Rectangle\>, System.IComparable\<Rectangle\>, System.IComparable { public readonly float Width; public readonly float Length; public override int \_Tag =\> 1; public Rectangle(float width, float length) { this.Width = width; this.Length = length; } public void Deconstruct(out float Width, out float Length) { Width = this.Width; Length = this.Length; } public Rectangle(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { Width = (float)info.GetValue("Width", typeof(float)); Length = (float)info.GetValue("Length", typeof(float)); } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("Width", Width); info.AddValue("Length", Length); } public static bool operator ==(Rectangle x, Rectangle y) =\> ReferenceEquals(x, y) || (x?.Equals(y) ?? false); public static bool operator !=(Rectangle x, Rectangle y) =\> !(x == y); public static bool operator\>(Rectangle x, Rectangle y) =\> !ReferenceEquals(x, y) && !ReferenceEquals(x, null) && x.CompareTo(y) \> 0; public static bool operator \<(Rectangle x, Rectangle y) =\> !ReferenceEquals(x, y) && (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \< 0); public static bool operator \>=(Rectangle x, Rectangle y) =\> ReferenceEquals(x, y) || (!ReferenceEquals(x, null) && x.CompareTo(y) \>= 0); public static bool operator \<=(Rectangle x, Rectangle y) =\> ReferenceEquals(x, y) || (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \<= 0); public bool Equals(Rectangle other) { if (LanguageExt.Prelude.isnull(other)) return false; if (!default(EqDefault\<float\>).Equals(Width, other.Width)) return false; if (!default(EqDefault\<float\>).Equals(Length, other.Length)) return false; return true; } public override bool Equals(object obj) =\> obj is Rectangle tobj && Equals(tobj); public override bool Equals(Shape obj) =\> obj is Rectangle tobj && Equals(tobj); public override int CompareTo(object obj) =\> obj is Shape p ? CompareTo(p) : 1; public override int CompareTo(Shape obj) =\> obj is Rectangle tobj ? CompareTo(tobj) : obj is null ? 1 : \_Tag.CompareTo(obj.\_Tag); public int CompareTo(Rectangle other) { if (LanguageExt.Prelude.isnull(other)) return 1; int cmp = 0; cmp = default(OrdDefault\<float\>).Compare(Width, other.Width); if (cmp != 0) return cmp; cmp = default(OrdDefault\<float\>).Compare(Length, other.Length); if (cmp != 0) return cmp; return 0; } public override int GetHashCode() { const int fnvOffsetBasis = -2128831035; const int fnvPrime = 16777619; int state = fnvOffsetBasis; unchecked { state = (default(EqDefault\<float\>).GetHashCode(Width) ^ state) \* fnvPrime; state = (default(EqDefault\<float\>).GetHashCode(Length) ^ state) \* fnvPrime; } return state; } public override string ToString() { var sb = new StringBuilder(); sb.Append("Rectangle("); sb.Append(LanguageExt.Prelude.isnull(Width) ? $"Width: [null]" : $"Width: {Width}"); sb.Append($", "); sb.Append(LanguageExt.Prelude.isnull(Length) ? $"Length: [null]" : $"Length: {Length}"); sb.Append(")"); return sb.ToString(); } public Rectangle With(float? Width = null, float? Length = null) =\> new Rectangle(Width ?? this.Width, Length ?? this.Length); public static readonly Lens\<Rectangle, float\> width = Lens\<Rectangle, float\>.New(\_x =\> \_x.Width, \_x =\> \_y =\> \_y.With(Width: \_x)); public static readonly Lens\<Rectangle, float\> length = Lens\<Rectangle, float\>.New(\_x =\> \_x.Length, \_x =\> \_y =\> \_y.With(Length: \_x)); } [System.Serializable] public sealed class Circle : \_ShapeBase, System.IEquatable\<Circle\>, System.IComparable\<Circle\>, System.IComparable { public readonly float Radius; public override int \_Tag =\> 2; public Circle(float radius) { this.Radius = radius; } public void Deconstruct(out float Radius) { Radius = this.Radius; } public Circle(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { Radius = (float)info.GetValue("Radius", typeof(float)); } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("Radius", Radius); } public static bool operator ==(Circle x, Circle y) =\> ReferenceEquals(x, y) || (x?.Equals(y) ?? false); public static bool operator !=(Circle x, Circle y) =\> !(x == y); public static bool operator\>(Circle x, Circle y) =\> !ReferenceEquals(x, y) && !ReferenceEquals(x, null) && x.CompareTo(y) \> 0; public static bool operator \<(Circle x, Circle y) =\> !ReferenceEquals(x, y) && (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \< 0); public static bool operator \>=(Circle x, Circle y) =\> ReferenceEquals(x, y) || (!ReferenceEquals(x, null) && x.CompareTo(y) \>= 0); public static bool operator \<=(Circle x, Circle y) =\> ReferenceEquals(x, y) || (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \<= 0); public bool Equals(Circle other) { if (LanguageExt.Prelude.isnull(other)) return false; if (!default(EqDefault\<float\>).Equals(Radius, other.Radius)) return false; return true; } public override bool Equals(object obj) =\> obj is Circle tobj && Equals(tobj); public override bool Equals(Shape obj) =\> obj is Circle tobj && Equals(tobj); public override int CompareTo(object obj) =\> obj is Shape p ? CompareTo(p) : 1; public override int CompareTo(Shape obj) =\> obj is Circle tobj ? CompareTo(tobj) : obj is null ? 1 : \_Tag.CompareTo(obj.\_Tag); public int CompareTo(Circle other) { if (LanguageExt.Prelude.isnull(other)) return 1; int cmp = 0; cmp = default(OrdDefault\<float\>).Compare(Radius, other.Radius); if (cmp != 0) return cmp; return 0; } public override int GetHashCode() { const int fnvOffsetBasis = -2128831035; const int fnvPrime = 16777619; int state = fnvOffsetBasis; unchecked { state = (default(EqDefault\<float\>).GetHashCode(Radius) ^ state) \* fnvPrime; } return state; } public override string ToString() { var sb = new StringBuilder(); sb.Append("Circle("); sb.Append(LanguageExt.Prelude.isnull(Radius) ? $"Radius: [null]" : $"Radius: {Radius}"); sb.Append(")"); return sb.ToString(); } public Circle With(float? Radius = null) =\> new Circle(Radius ?? this.Radius); public static readonly Lens\<Circle, float\> radius = Lens\<Circle, float\>.New(\_x =\> \_x.Radius, \_x =\> \_y =\> \_y.With(Radius: \_x)); } [System.Serializable] public sealed class Prism : \_ShapeBase, System.IEquatable\<Prism\>, System.IComparable\<Prism\>, System.IComparable { public readonly float Width; public readonly float Height; public override int \_Tag =\> 3; public Prism(float width, float height) { this.Width = width; this.Height = height; } public void Deconstruct(out float Width, out float Height) { Width = this.Width; Height = this.Height; } public Prism(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { Width = (float)info.GetValue("Width", typeof(float)); Height = (float)info.GetValue("Height", typeof(float)); } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("Width", Width); info.AddValue("Height", Height); } public static bool operator ==(Prism x, Prism y) =\> ReferenceEquals(x, y) || (x?.Equals(y) ?? false); public static bool operator !=(Prism x, Prism y) =\> !(x == y); public static bool operator\>(Prism x, Prism y) =\> !ReferenceEquals(x, y) && !ReferenceEquals(x, null) && x.CompareTo(y) \> 0; public static bool operator \<(Prism x, Prism y) =\> !ReferenceEquals(x, y) && (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \< 0); public static bool operator \>=(Prism x, Prism y) =\> ReferenceEquals(x, y) || (!ReferenceEquals(x, null) && x.CompareTo(y) \>= 0); public static bool operator \<=(Prism x, Prism y) =\> ReferenceEquals(x, y) || (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \<= 0); public bool Equals(Prism other) { if (LanguageExt.Prelude.isnull(other)) return false; if (!default(EqDefault\<float\>).Equals(Width, other.Width)) return false; if (!default(EqDefault\<float\>).Equals(Height, other.Height)) return false; return true; } public override bool Equals(object obj) =\> obj is Prism tobj && Equals(tobj); public override bool Equals(Shape obj) =\> obj is Prism tobj && Equals(tobj); public override int CompareTo(object obj) =\> obj is Shape p ? CompareTo(p) : 1; public override int CompareTo(Shape obj) =\> obj is Prism tobj ? CompareTo(tobj) : obj is null ? 1 : \_Tag.CompareTo(obj.\_Tag); public int CompareTo(Prism other) { if (LanguageExt.Prelude.isnull(other)) return 1; int cmp = 0; cmp = default(OrdDefault\<float\>).Compare(Width, other.Width); if (cmp != 0) return cmp; cmp = default(OrdDefault\<float\>).Compare(Height, other.Height); if (cmp != 0) return cmp; return 0; } public override int GetHashCode() { const int fnvOffsetBasis = -2128831035; const int fnvPrime = 16777619; int state = fnvOffsetBasis; unchecked { state = (default(EqDefault\<float\>).GetHashCode(Width) ^ state) \* fnvPrime; state = (default(EqDefault\<float\>).GetHashCode(Height) ^ state) \* fnvPrime; } return state; } public override string ToString() { var sb = new StringBuilder(); sb.Append("Prism("); sb.Append(LanguageExt.Prelude.isnull(Width) ? $"Width: [null]" : $"Width: {Width}"); sb.Append($", "); sb.Append(LanguageExt.Prelude.isnull(Height) ? $"Height: [null]" : $"Height: {Height}"); sb.Append(")"); return sb.ToString(); } public Prism With(float? Width = null, float? Height = null) =\> new Prism(Width ?? this.Width, Height ?? this.Height); public static readonly Lens\<Prism, float\> width = Lens\<Prism, float\>.New(\_x =\> \_x.Width, \_x =\> \_y =\> \_y.With(Width: \_x)); public static readonly Lens\<Prism, float\> height = Lens\<Prism, float\>.New(\_x =\> \_x.Height, \_x =\> \_y =\> \_y.With(Height: \_x)); } public static partial class ShapeCon { public static Shape Rectangle(float width, float length) =\> new Rectangle(width, length); public static Shape Circle(float radius) =\> new Circle(radius); public static Shape Prism(float width, float height) =\> new Prism(width, height); } [System.Serializable] public abstract partial class Shape : IEquatable\<Shape\>, IComparable\<Shape\>, IComparable { public abstract int \_Tag { get; } public abstract int CompareTo(object obj); public abstract int CompareTo(Shape other); public abstract bool Equals(Shape other); public override bool Equals(object obj) =\> obj is Shape tobj && Equals(tobj); public override int GetHashCode() =\> throw new System.NotSupportedException(); public static bool operator ==(Shape x, Shape y) =\> ReferenceEquals(x, y) || (x?.Equals(y) ?? false); public static bool operator !=(Shape x, Shape y) =\> !(x == y); public static bool operator\>(Shape x, Shape y) =\> !ReferenceEquals(x, y) && !ReferenceEquals(x, null) && x.CompareTo(y) \> 0; public static bool operator \<(Shape x, Shape y) =\> !ReferenceEquals(x, y) && (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \< 0); public static bool operator \>=(Shape x, Shape y) =\> ReferenceEquals(x, y) || (!ReferenceEquals(x, null) && x.CompareTo(y) \>= 0); public static bool operator \<=(Shape x, Shape y) =\> ReferenceEquals(x, y) || (ReferenceEquals(x, null) && !ReferenceEquals(y, null) || x.CompareTo(y) \<= 0); } public abstract partial class \_ShapeBase : Shape { public override Shape Rectangle(float width, float length) =\> throw new NotSupportedException(); public override Shape Circle(float radius) =\> throw new NotSupportedException(); public override Shape Prism(float width, float height) =\> throw new NotSupportedException(); }
    

    ๐Ÿšš > This will soon be adapted to support a [Record] attribute for generating records at compile-time and remove the need to derive from Record<TYPE>. That will mean struct records will be easy to create.

  • v3.3.32 Changes

    November 09, 2019

    ๐Ÿš€ Following on from last night's discriminated union feature release, I have added some additional features to the code-gen. The full-set of features available now are:

    • Structural equality (via lhs.Equals(rhs) - due to the base-type being an interface)
    • GetHashCode() implementation
    • ToString() implementation
    • Deconstructor method implemented for all union case types
    • With implemented for all union case types
    • Lenses for all fields within a case type
    • ๐Ÿ‘Œ Improved error reporting for the code-gen system as a whole

    And so, now this:

     [Union] public interface Shape { Shape Rectangle(float width, float length); Shape Circle(float radius); Shape Prism(float width, float height); }
    

    Will generate this:

    public partial class Rectangle : LanguageExt.Record\<Rectangle\>, Shape { public readonly float Width; public readonly float Length; public Rectangle(float width, float length) { this.Width = width; this.Length = length; } public void Deconstruct(out float Width, out float Length) { Width = this.Width; Length = this.Length; } public Rectangle With(float? Width = null, float? Length = null) =\> new Rectangle(Width ?? this.Width, Length ?? this.Length); public static readonly Lens\<Rectangle, float\> width = Lens\<Rectangle, float\>.New(\_x =\> \_x.Width, \_x =\> \_y =\> \_y.With(Width: \_x)); public static readonly Lens\<Rectangle, float\> length = Lens\<Rectangle, float\>.New(\_x =\> \_x.Length, \_x =\> \_y =\> \_y.With(Length: \_x)); Shape Shape.Rectangle(float width, float length) =\> throw new System.NotSupportedException(); Shape Shape.Circle(float radius) =\> throw new System.NotSupportedException(); Shape Shape.Prism(float width, float height) =\> throw new System.NotSupportedException(); } public partial class Circle : LanguageExt.Record\<Circle\>, Shape { public readonly float Radius; public Circle(float radius) { this.Radius = radius; } public void Deconstruct(out float Radius) { Radius = this.Radius; } public Circle With(float? Radius = null) =\> new Circle(Radius ?? this.Radius); public static readonly Lens\<Circle, float\> radius = Lens\<Circle, float\>.New(\_x =\> \_x.Radius, \_x =\> \_y =\> \_y.With(Radius: \_x)); Shape Shape.Rectangle(float width, float length) =\> throw new System.NotSupportedException(); Shape Shape.Circle(float radius) =\> throw new System.NotSupportedException(); Shape Shape.Prism(float width, float height) =\> throw new System.NotSupportedException(); } public partial class Prism : LanguageExt.Record\<Prism\>, Shape { public readonly float Width; public readonly float Height; public Prism(float width, float height) { this.Width = width; this.Height = height; } public void Deconstruct(out float Width, out float Height) { Width = this.Width; Height = this.Height; } public Prism With(float? Width = null, float? Height = null) =\> new Prism(Width ?? this.Width, Height ?? this.Height); public static readonly Lens\<Prism, float\> width = Lens\<Prism, float\>.New(\_x =\> \_x.Width, \_x =\> \_y =\> \_y.With(Width: \_x)); public static readonly Lens\<Prism, float\> height = Lens\<Prism, float\>.New(\_x =\> \_x.Height, \_x =\> \_y =\> \_y.With(Height: \_x)); Shape Shape.Rectangle(float width, float length) =\> throw new System.NotSupportedException(); Shape Shape.Circle(float radius) =\> throw new System.NotSupportedException(); Shape Shape.Prism(float width, float height) =\> throw new System.NotSupportedException(); } public static partial class ShapeCon { public static Shape Rectangle(float width, float length) =\> new Rectangle(width, length); public static Shape Circle(float radius) =\> new Circle(radius); public static Shape Prism(float width, float height) =\> new Prism(width, height); }
    
  • v3.3.30 Changes

    November 09, 2019

    ๐Ÿš€ In this release the code-generation story has been extended to support sum-types (also known as 'discriminated unions', 'union types', or 'case types').

    Simply declare an interface with the attribute [Union] where all methods declared in the interface return the type of the interface, i.e.

     [Union] public interface Maybe\<A\> { Maybe\<A\> Just(A value); Maybe\<A\> Nothing(); }
    

    It has similar behaviour to this, in F#:

    type Maybe\<'a\> =| Just of 'a | Nothing
    

    In the above example, two case-types classes will be created Just<A> and Nothing<A> as well as static constructor class called Maybe:

    var maybe = Maybe.Just(123); var res = maybe switch { Just\<int\> just =\> just.Value, Nothing\<int\> \_ =\> 0 };
    

    This is the generated code:

    public partial class Just\<A\> : LanguageExt.Record\<Just\<A\>\>, Maybe\<A\> { public readonly A Value; Maybe\<A\> Maybe\<A\>.Just(A value) =\> throw new System.NotSupportedException(); Maybe\<A\> Maybe\<A\>.Nothing() =\> throw new System.NotSupportedException(); public Just(A value) { Value = value; } } public partial class Nothing\<A\> : LanguageExt.Record\<Nothing\<A\>\>, Maybe\<A\> { Maybe\<A\> Maybe\<A\>.Just(A value) =\> throw new System.NotSupportedException(); Maybe\<A\> Maybe\<A\>.Nothing() =\> throw new System.NotSupportedException(); public Nothing() { } } public static partial class Maybe { public static Maybe\<A\> Just\<A\>(A value) =\> new Just\<A\>(value); public static Maybe\<A\> Nothing\<A\>() =\> new Nothing\<A\>(); }
    

    ๐Ÿ‘ The generated code is relatively basic at the moment. It will be extended to support abstract class types and will auto-generate structural equality behaviour as well as other useful behaviours. But for now this is a super-quick way to generate the cases for a union-type and have a simple way of constructing them.

    The generated types are all partial and can therefore be extended trivially.

    Here's another simple example:

     [Union] public interface Shape { Shape Rectangle(float width, float length); Shape Circle(float radius); Shape Prism(float width, float height); }
    

    And the generated code:

    public partial class Rectangle : LanguageExt.Record\<Rectangle\>, Shape { public readonly float Width; public readonly float Length; Shape Shape.Rectangle(float width, float length) =\> throw new System.NotSupportedException(); Shape Shape.Circle(float radius) =\> throw new System.NotSupportedException(); Shape Shape.Prism(float width, float height) =\> throw new System.NotSupportedException(); public Rectangle(float width, float length) { Width = width; Length = length; } } public partial class Circle : LanguageExt.Record\<Circle\>, Shape { public readonly float Radius; Shape Shape.Rectangle(float width, float length) =\> throw new System.NotSupportedException(); Shape Shape.Circle(float radius) =\> throw new System.NotSupportedException(); Shape Shape.Prism(float width, float height) =\> throw new System.NotSupportedException(); public Circle(float radius) { Radius = radius; } } public partial class Prism : LanguageExt.Record\<Prism\>, Shape { public readonly float Width; public readonly float Height; Shape Shape.Rectangle(float width, float length) =\> throw new System.NotSupportedException(); Shape Shape.Circle(float radius) =\> throw new System.NotSupportedException(); Shape Shape.Prism(float width, float height) =\> throw new System.NotSupportedException(); public Prism(float width, float height) { Width = width; Height = height; } } public static partial class ShapeCon { public static Shape Rectangle(float width, float length) =\> new Rectangle(width, length); public static Shape Circle(float radius) =\> new Circle(radius); public static Shape Prism(float width, float height) =\> new Prism(width, height); }
    

    โšก๏ธ > NOTE: The code-gen doesn't yet support .NET Core 3.0 - I'm still waiting for the Roslyn code-gen project to be updated. If it isn't forthcoming soon, I'll look for other options.

  • v3.3.28 Changes

    September 28, 2019

    ๐Ÿš€ release

    Record types now have an improved hash-code algorithm, based on the FNV 1a hashing algorithm

    Reader and RWS code-generators will now look for existing methods with the same name as the one they're going to generate. If existing methods exist then the methods won't be generated.

    - This allows for things like bespoke Bind implementations without having to build everything by hand.

    Map, Select, and SelectMany are now implemented with Bind. So they will also leverage any bespoke Bind methods.

    โšก๏ธ Where is implemented with Filter, which means providing a bespoke Filter method will also update the Where

    โž• Added Match(Action<A> Succ, Action<Error> Fail) for side-effecting matching to RWSResult and ReaderResult

    โž• Added IfFailThrow() to RWSResult and ReaderResult

    ๐Ÿ› Bug fix: for RWS.Run which was still returning a tuple after the refactor for better error handling. It now returns RWSResult.

    Bug fix: Where implementation typo for Arr<A>

    ๐Ÿ›  Thanks to: @alaendle and @EdruptCo for the fixes.

    ๐Ÿš€ non-release notes

    There is a new sample based on the famous Contoso app. @blakeSaucier has kindly converted it over to be more functional, using many of the features of language-ext. Going forward I will try and de-interface it and make it more pure and monadic, but this is a fantastic starting point to give developers guidance on how they can be more functional with their C# code.

  • v3.3.22 Changes

    September 17, 2019

    โšก๏ธ This is a small update to the LanguageExt.CodeGen package to improve the namespacing of types and methods in the generated RWS and Reader monad code (so you don't have to provide the namespaces manually).

  • v3.3.21 Changes

    September 15, 2019

    ๐Ÿš€ As with the previous release that refactored the Reader monad to have better error handling. I have now done the same for the RWS monad.

    ๐Ÿ’ฅ Breaking changes

    • RWS doesn't now return a tuple and instead returns RWSResult<MonoidW, R, W, S, A> which has a lot of the same functionality as ReaderResult<A> but with additional functionality bespoke to the RWS monad (ToReader(), ToWriter(), ToState()).
    • The old RWSResult static class has been replaced and you should now use the RWS and RWSFail constructors in the Prelude to construct the pure and failure monads.

    Code-gen

    โšก๏ธ The LanguageExt.CodeGen library has been updated to work with the new RWS monad and is the easiest way to work with Reader and RWS monads (Writer and State will be added soon).

    For those that have missed it, this:

    namespace TestBed{ [RWS(WriterMonoid: typeof(MSeq\<string\>), Env: typeof(IO), State: typeof(Person), Constructor: "Pure", Fail: "Error" )] public partial struct Subsys\<T\> { } }
    

    Will generate:

    namespace TestBed{ public partial struct Subsys\<T\> { readonly internal LanguageExt.RWS\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\> \_\_comp; internal Subsys(LanguageExt.RWS\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\> comp) =\> \_\_comp = comp; public static Subsys\<T\> Pure(T value) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, value)); public static Subsys\<T\> Error() =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, LanguageExt.Common.Error.Bottom)); public static Subsys\<T\> Error(string message) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, LanguageExt.Common.Error.New(message))); public static Subsys\<T\> Error(Exception exception) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, LanguageExt.Common.Error.New(exception))); public static Subsys\<T\> Error(LanguageExt.Common.Error error) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, error)); public static Subsys\<T\> Error(string message, Exception exception) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, LanguageExt.Common.Error.New(message, exception))); public Subsys\<U\> Map\<U\>(Func\<T, U\> f) =\> new Subsys\<U\>(\_\_comp.Map(f)); public Subsys\<U\> Select\<U\>(Func\<T, U\> f) =\> new Subsys\<U\>(\_\_comp.Map(f)); public Subsys\<U\> Bind\<U\>(Func\<T, Subsys\<U\>\> f) =\> new Subsys\<U\>(\_\_comp.Bind(a =\> f(a).\_\_comp)); public Subsys\<U\> SelectMany\<U\>(Func\<T, Subsys\<U\>\> f) =\> new Subsys\<U\>(\_\_comp.Bind(a =\> f(a).\_\_comp)); public Subsys\<V\> SelectMany\<U, V\>(Func\<T, Subsys\<U\>\> bind, Func\<T, U, V\> project) =\> new Subsys\<V\>(\_\_comp.Bind(a =\> bind(a).\_\_comp.Map(b =\> project(a, b)))); public (TryOption\<T\> Value, LanguageExt.Seq\<string\> Output, TestBed.Person State) Run(TestBed.IO env, TestBed.Person state) =\> \_\_comp.Run(env, state); public Subsys\<T\> Filter(Func\<T, bool\> f) =\> new Subsys\<T\>(\_\_comp.Where(f)); public Subsys\<T\> Where(Func\<T, bool\> f) =\> new Subsys\<T\>(\_\_comp.Where(f)); public Subsys\<T\> Do(Action\<T\> f) =\> new Subsys\<T\>(\_\_comp.Do(f)); public Subsys\<T\> Strict() =\> new Subsys\<T\>(\_\_comp.Strict()); public Seq\<T\> ToSeq(TestBed.IO env, TestBed.Person state) =\> \_\_comp.ToSeq(env, state); public Subsys\<LanguageExt.Unit\> Iter(Action\<T\> f) =\> new Subsys\<LanguageExt.Unit\>(\_\_comp.Iter(f)); public Func\<TestBed.IO, TestBed.Person, State\> Fold\<State\>(State state, Func\<State, T, State\> f) { var self = this; return (env, s) =\> self.\_\_comp.Fold(state, f).Run(env, s).Value.IfNoneOrFail(state); } public Func\<TestBed.IO, TestBed.Person, bool\> ForAll(Func\<T, bool\> f) { var self = this; return (env, s) =\> self.\_\_comp.ForAll(f).Run(env, s).Value.IfNoneOrFail(false); } public Func\<TestBed.IO, TestBed.Person, bool\> Exists(Func\<T, bool\> f) { var self = this; return (env, s) =\> self.\_\_comp.Exists(f).Run(env, s).Value.IfNoneOrFail(false); } public Subsys\<T\> Local(Func\<TestBed.IO, TestBed.IO\> f) =\> new Subsys\<T\>(LanguageExt.Prelude.local\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>(\_\_comp, f)); public Subsys\<(T, U)\> Listen\<U\>(Func\<LanguageExt.Seq\<string\>, U\> f) =\> new Subsys\<(T, U)\>(\_\_comp.Listen(f)); public Subsys\<T\> Censor(Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\> f) =\> new Subsys\<T\>(\_\_comp.Censor(f)); } public static partial class Subsys { public static Subsys\<T\> Pure\<T\>(T value) =\> Subsys\<T\>.Pure(value); public static Subsys\<T\> Error\<T\>() =\> Subsys\<T\>.Error(); public static Subsys\<T\> Error\<T\>(string message) =\> Subsys\<T\>.Error(message); public static Subsys\<T\> Error\<T\>(string message, Exception exception) =\> Subsys\<T\>.Error(message, exception); public static Subsys\<T\> Error\<T\>(Exception exception) =\> Subsys\<T\>.Error(exception); public static Subsys\<T\> Error\<T\>(LanguageExt.Common.Error error) =\> Subsys\<T\>.Error(error); public static Subsys\<T\> asks\<T\>(Func\<TestBed.IO, T\> f) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, f(env))); public static Subsys\<TestBed.IO\> ask =\> new Subsys\<TestBed.IO\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, TestBed.IO\>.New(state, env)); public static Subsys\<TestBed.Person\> get =\> new Subsys\<TestBed.Person\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, TestBed.Person\>.New(state, state)); public static Subsys\<T\> gets\<T\>(Func\<TestBed.Person, T\> f) =\> new Subsys\<T\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>.New(state, f(state))); public static Subsys\<LanguageExt.Unit\> put(TestBed.Person value) =\> new Subsys\<LanguageExt.Unit\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, LanguageExt.Unit\>.New(value, default(LanguageExt.Unit))); public static Subsys\<LanguageExt.Unit\> modify(Func\<TestBed.Person, TestBed.Person\> f) =\> new Subsys\<LanguageExt.Unit\>((env, state) =\> LanguageExt.RWSResult\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, LanguageExt.Unit\>.New(f(state), default(LanguageExt.Unit))); public static Subsys\<T\> local\<T\>(Subsys\<T\> ma, Func\<TestBed.IO, TestBed.IO\> f) =\> ma.Local(f); public static Subsys\<T\> Pass\<T\>(this Subsys\<(T, Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\>)\> ma) =\> new Subsys\<T\>(ma.\_\_comp.Pass()); public static Subsys\<T\> pass\<T\>(Subsys\<(T, Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\>)\> ma) =\> new Subsys\<T\>(ma.\_\_comp.Pass()); public static Subsys\<(T, U)\> listen\<T, U\>(Subsys\<T\> ma, Func\<LanguageExt.Seq\<string\>, U\> f) =\> ma.Listen(f); public static Subsys\<T\> censor\<T\>(Subsys\<T\> ma, Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\> f) =\> ma.Censor(f); public static Subsys\<LanguageExt.Unit\> tell(LanguageExt.Seq\<string\> what) =\> new Subsys\<LanguageExt.Unit\>(tell\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, LanguageExt.Unit\>(what)); public static Subsys\<LanguageExt.Seq\<string\>\> ReadAllLines(string fileName) =\> ask.Map(\_\_env =\> \_\_env.ReadAllLines(fileName)); public static Subsys\<LanguageExt.Unit\> WriteAllLines(string fileName, LanguageExt.Seq\<string\> lines) =\> ask.Map(\_\_env =\> \_\_env.WriteAllLines(fileName, lines)); public static Subsys\<int\> Zero =\> ask.Map(\_\_env =\> \_\_env.Zero); public static Subsys\<string\> Name =\> get.Map(\_\_env =\> \_\_env.Name); public static Subsys\<string\> Surname =\> get.Map(\_\_env =\> \_\_env.Surname); } }
    

    Thereby making it much easier to work with the RWS monad.

  • v3.3.19 Changes

    August 21, 2019

    Reader

    The Reader monad now has more advanced error handling (which is also reflected in the code-gen that wraps the Reader monad).

    ๐Ÿšš This means Reader<Env, A>.Run(env) now returns ReaderResult<A> instead of TryOption<A>. I have also removed Filter and Where from Reader and instead you should use:

    from x in success? Reader\<Env, A\>(successValue) : ReaderFail\<Env, A\>("Fail message") ...
    

    Filter and Where would return a result in a Bottom state, which isn't ideal. Obviously if you need this functionality back then you can create the extension methods yourself to create a similar functionality:

    public static Reader\<Env, A\> Where\<Env, A\>(this Reader\<Env, A\> ma, Func\<A, bool\> f) =\>ma.Bind(a =\> f(a) ? Reader\<Env, A\>(a) : ReaderFail\<Env, A\>(BottomException.Default));
    

    Fail states can be created using:

    ReaderFail\<Env, A\>(string message); ReaderFail\<Env, A\>(string message, Exception exception); ReaderFail\<Env, A\>(Exception exception);
    

    ReaderResult

    ReaderResult<A> has Match and IfNone (so replace previous usage of IfNoneOrFail with IfNone). It also has conversion methods: ToSeq(), ToList(), ToOption(), ToOptionUnsafe(), ToOptionAsync(), ToEither(), ToEither(Func<Error, L> f), ToEitherUnsafe(), ToEitherUnsafe(Func<Error, L> f), ToEitherAsync(), ToEitherAsync(Func<Error, L> f), ToTry(), ToTryAsync()

    Error

    ๐Ÿ‘ป To facilitate the better error handling I needed to add a new Error type. The chances of this clashing with user's code is large, so it has been put into a new namespace: LanguageExt.Common. It can hold a message, a status value int, and an Exception.

    Result

    ๐Ÿ‘€ It seems to me that Result, OptionalResult, and Error belong in the same namespace so Result and OptionalResult have been moved to LanguageExt.Common.

    ๐Ÿ‘€ On the whole you shouldn't really see Error or Result, most of the time you'll just be doing member access - and so the need to include the LanguageExt.Common should be rare.

  • v3.3.16 Changes

    August 18, 2019

    โšก๏ธ Unfortunately the previous fix to the HashMap and HashSet didn't catch all cases. This has now been updated and thoroughly tested.

    ๐Ÿš€ If you're running any release of lang-ext since v3.3.0 (and using HashMap or HashSet) then it is advisable that you upgrade as soon as possible.

  • v3.3.14

    August 18, 2019