Major Version Release Notes/Version 5/README.md
WORK IN PROGRESS NOTES
Version 5 of language-ext is huge. It is both massive in terms of new capabilities delivered but also in breaking changes that will require you to spend some time fixing up compilation failures and making judgements about your code. An upgrade to v5 should be considered a significant undertaking for a large code-base - please make sure you have the time before upgrading.
I have made copious notes below to help you understand the changes and to ease the migration. If, during the migration you encounter issues that are not in these notes, please let me know so I can update them for everybody else.
Further contextual information about the updates can be found on my blog -- it digs a little deeper into the new capabilities and hopefully explains why these changes are necessary.
Language-ext is 10 years old this year, so please consider this my once per decade refresh. It is very much my .NET Framework to .NET Core moment.
Seq1 made [Obsolete]Semigroup<A> and Monoid<A> types have been refactoredTypeClass class has been renamed TraitApply extensions that use raw Func removedSequence extension methods have been removed (#1)Sequence extension methods have been removed (#2)Sequence extension methods have been removed (#3)ToComparer doesn't exist on the Ord<A> trait any moreLanguageExt.ClassInstances.SumGuard<E> has become Guard<E, A>HasCancel<RT> should be replaced with HasIO<RT>UnitsOfMeasaure namespace converted to a static classEither doesn't support IEnumerable<EitherData> any moreEither 'bi' functions have their arguments flippedTuple and KeyValuePair removedIf you spend any time with Haskell or other languages with higher-kinds, you'll notice a heavy use of higher-kinded polymorphism. From functors, applicatives, monads, foldables, traversables, monad transformers, etc. This flavour of polymorphism allows for compositional superpowers that we can only dream of in C#.
Composition of pure functional components always leads to new, more capable components, that are also automatically pure. This is the true power of pure functional composition; unlike OO composition that just collects potential complexity, pure functional composition abstracts away from it. You can trust the composition to be sound.
Much of this library up until this point has been trying to give you the capability to build these compositions. It's been achieved so far by lots of typing by me and lots of code-gen. For example, the LanguageExt.Transformers library was a code-genned mass of source-code that combined every monadic type with every other monadic type so that you can work on them nested. It was 200,000 lines of generated code - and was getting exponentially worse.
I've also written 100s (maybe 1000s) of Select and SelectMany implementations that give you the impression that C# has generalised monads. But it doesn't, it's just a lot of typing. If you create your own monadic type it won't magically work with any existing language-ext types and you can't write general functions that accept any monad and have it just work.
And that is very much because C# doesn't support higher-kinded polymorphism.
That means that you pretty much only get to use what I provide. That's not acceptable to me. I want to empower everybody to be able to leverage pure functional composition in the same way that can be done in Haskell (and other languages that have higher-kinds). A series of articles on higher-kinds features on on my blog.
async (green threads)If I continued the way I was before then every monadic type would have an *Async variant, as would every method and function. This was getting out of hand. For something like an IO/effect monad where you could also have an optional error-type and optional runtime-type that meant 8 types. Each with 1000s of lines of code to define them. Then, when you think there's 20 or so 30 or so monadic types, it becomes a big maintenence problem. There's also issues around consistency between each type (making sure everything has a MapAsync, BindAsync, etc.) - as well as making sure sync types can work with async types, etc.
So, as of now, this library stands against 'declarative async' - i.e. we are adopting a 'green threads mentality'. That is we will not be giving you *Async variants of anything. All IO computation types (IO and Eff) will support the lifting of both synchronous and asynchronous functions, but you won't see evidence of asynchronicity in any type-signatures.
Those types each have a Run() function which appear to run synchronously, i.e. they don't return a Task<A>. In fact, they don't run synchronously, they run concurrently. Internally, they use similar mechanics to Task to yield time to your current thread whilst waiting for their own IO operations to complete. So, calling operation.Run() is the same as calling await operation.RunAsync() - you just don't need the rest of your code infected by async.
When you want an operation not to run concurrently, but in parallel instead (i.e. queue the work to be run on the next available ThreadPool thread), you can call operation.Fork(). It supports fire-and-forget, so operation.Fork().Run() returns immediately, or you can await the result:
var forkedOperation = from f in operation.Fork() // runs in parallel
from r in f.Await // use the ForkIO to await the result
select r;
// This will yield the current thread to allow concurrency, whilst the forked
// operation runs on another thread.
var result = forkedOperation.Run();
To lift an existing Task based function into these types you can just call:
liftIO(Func<EnvIO, Task<A>> function)liftIO(Func<Task<A>> function)Which are both in the Prelude and allow an IO operation to be lifted into any monad that supports IO. EnvIO gives you access to the CancellationToken, CancellationTokenSource, and SynchronizationConrext.
For example:
var operation = from text in liftIO(env => File.ReadAllTextAsync(path, env.Token))
let lines = text.Split("\r\n").ToSeq()
select lines;
We can then run that operation:
Seq<string> result = operation.Run();
And we get a concrete Seq<string> - not a Task<Seq<string>>, but the operation ran concurrently.
The library has been held back by the need to support .NET Framework. As of now this library is .NET (formerly known as .NET Core) only. Instantly jumping to .NET 8.0 (which has Long Term Support).
This opens up: static interface members (which allows the trait/ad-hoc polymorphism support to get a power-up) and collection initialisers for all of the immutable collections - amongst others.
So, what is higher-kinded polymorphism? Think of a function like this:
static Option<int> AddOne(Option<int> mx) =>
mx.Map(x => x + 1);
That's a function that takes an Option and leverages its Map function to add one to the value inside. The Map function makes it a 'Functor'. Functors map.
But, if functors map, and all functors have a Map method, why can't I write:
static F<int> AddOne<F>(F<int> mx) where F : Functor =>
mx.Map(x => x + 1);
It's because we can only make the lower-kind polymorphic. For example, I can write a function like this:
static Option<string> Show(Option<A> mx) =>
mx.Map(x => x.ToString());
Where the lower-kind, the A, is parametric - but not the higher-kind (the F in the previous example).
You might think we could just do this with interfaces:
interface Functor<A>
{
Functor<B> Map<B>(Func<A, B> f);
}
public class Option<A> : Functor<A>
{
// ...
}
public class Seq<A> : Functor<A>
{
// ...
}
On the surface, that looks like we can then just accept Functor<A> and call map on it. The problem is that we really shouldn't mix and match different types of functor (same with monads, applicatives, etc). We've lost the information on what's inside the functor. Every time we call Map on Option it stops being an Option, so we can't then call Bind, or any other useful functions, we're stuck doing Map forever.
C# has recently introduced static interface members. It allows us to create 'trait types'. Users of language-ext know this approach from the TypeClasses and ClassInstances technique. But, with static interface methods the approach has become much more elegant and usable.
language-ext is now .NET 8 only - mostly to leverage static interface methods
So, now what we can do is define a really simple type:
public interface K<F, A>
This one type will change your life!
Remember, we could't create a higher-kinded type like F<A>, but we can create this: K<F, A>. It has no members, it's a completely empty interface, we simply use this as a 'marker' for our types so that they can leverage higher-kinded polymorphism. If you look at any of the major types in language-ext v5 you'll see the K type being used (K is short for 'Kind'):
This is Option<A>:
public readonly struct Option<A> :
K<Option, A>
{
...
}
It locks its F type to be Option (notice the lack of an A, it's just Option).
If you then go and look at Option, you'll notice it inherits some really interesting interfaces!
public class Option :
Monad<Option>,
Traversable<Option>,
Alternative<Option>
{
// ...
}
Monad inherits Applicative and FunctorTraversable inherits Functor and FoldableAnd, if you and look at those interfaces you'll see the static interface methods that all leverage the K kind-type:
public interface Functor<F>
where F : Functor<F>
{
public static abstract K<F, B> Map<A, B>(Func<A, B> f, K<F, A> ma);
}
Now, let's look at the Haskell defintion of Functor:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Notice how the type is parameterised by f (just like Functor<F>), and how it takes a higher-kinded value of f a, which is the same as our K<F, A>, and it returns a higher-kinded value of f b, which is our K<F, B>.
We have the same defintion as Haskell. Exactly the same!
What does this mean? Well, we can now write generic functions that work with functors, applicatives, monads, monad-transformers, traversables, alternatives, foldables, state monads, reader monads, writer monads...
Here's the example from before:
public static K<F, int> AddOne<F>(K<F, int> mx) where F : Functor<F> =>
F.Map(x => x + 1, mx);
It calls the static Map function on the F trait, which is implemented by each type. Here's the implementation for Option
public class Option :
Monad<Option>,
Traversable<Option>,
Alternative<Option>
{
// ...
public static K<Option, B> Map<A, B>(Func<A, B> f, K<Option, A> ma) =>
ma.As().Map(f);
// ...
}
Remember, Option<A> inherits from K<Option, A>, so we can just downcast it and call the Option<A> implementation of Map. The As() method does the downcast from K<Option, A> to Option<A>.
You may think downcasting is a bit risky here, but really nothing else should inherit from
K<Option, A>. Doing so only makes sense forOption<A>. I think the risk of a casting issue is close to zero.
So, just to be clear. Every type, like Option<A>, Seq<A>, Eff<A>, etc. has a sibling type of the same name with the last generic parameter removed. So, Option<A> has a sibling type of Option, Seq<A> has Seq, etc. Those sibling types implement the traits, like Monad<M>, Functor<F>, etc. And, because Option<A>, Seq<A>, etc. all inherit from K<TRAIT, A> - where TRAIT is Option, Seq, Eff; this allows generic functions that have constriants like where F : Functor<F> to 'find' the bespoke implementation.
Types like
Either<L, R>, that have multiple generic arguments, again just lose the last argument for their sibling type:Either<L>.
Invoking the trait functions directly isn't that elegant - although perfectly usable - so there's extension methods that work with all of the abstract traits. Here's the above AddOne method rewritten to use the Map extension instead:
public static K<F, int> AddOne<F>(K<F, int> mx) where F : Functor<F> =>
mx.Map(x => x + 1);
So, let's try it out by calling it with a number of functors:
K<Option, int> mx = AddOne(Option<int>.Some(10));
K<Seq, int> my = AddOne(Seq<int>(1, 2, 3, 4));
K<Fin, int> mz = AddOne(Fin<int>.Succ(123));
Note, the return types have the 'trait' baked in. So you know it's still an option, seq, fin, etc.
Without full support for higher-kinds (from the C# language team) we can't do better than that.
However, there are extensions to help get back to the original type. Just call: As().
Option<int> mx = AddOne(Option<int>.Some(10)).As();
Seq<int> my = AddOne(Seq<int>(1, 2, 3, 4)).As();
Fin<int> mz = AddOne(Fin<int>.Succ(123)).As();
You only need to do that when you 'realise the concrete type'. Because the trait (Option, Seq, Fin, etc.) is the type that inherits Monad, Applicative, Traversable, etc. (not Option<A>, Seq<A>, Fin<A>, ) - you can just call their capabilities directly off the K value:
Option<int> mx = AddOne(Option<int>.Some(10))
.Bind(x => Option<int>.Some(x + 10))
.Map(x => x + 20)
.As();
Seq<int> mx = AddOne(Seq<int>(1, 2, 3, 4))
.Bind(x => Seq(x + 10))
.Map(x => x + 20)
.As();
Just this capability alone has alowed me to delete nearly 200,000 lines of generated code. That is incredible!
************ CONTINUE HERE ***************
The library has been held back by the need to support .NET Framework. As of now this library is .NET (formally known as .NET Core) only. Instantly jumping to .NET 8.0 (which has Long Term Support).
This opens up: static interface members (which allows the trait/ad-hoc polymorphism support to get a power-up) and collection initialisers for all of the immutable collections - amongst others.
many(stream) in any monad or monad-transformer expression you instantly turn the monad into a stream.IAsyncEnumerable, IEnumerable, IObservableuse / release)Pure / Fail monads
None)Seq<int> xs = [1, 2, 3] FTW!.netstandard2.0 no longer supportedVersion 5 of language-ext jumps straight to net8.0 support.
Motivation
I held off for as long as I could, but there are lots of new C# features that this library can make use of (primarily static interfaces, but others too like collection initialisers); so it's time to leave .NET Framework behind and focus on .NET [Core].
Impact
High (if you're still on .NET Framework)
Mitigation
Migrate your application to .NET Core
Seq1 made [Obsolete]A previous attempt to remove Seq1 was paused due to potential migration issues.
Motivation
The plan to remove the Seq1 singleton Seq constructor was announced a few years ago. I've taken this opportunity to make it obsolete as we now have collection initialisers and the previous reasons for the delay in making Seq1 obsolete have subsided (a 3 year window should be enough!).
Impact
Low
Mitigation
Use [x] or Seq.singleton(x)
Error no longer implicitly convertable from StringIt seemed like a good idea at the time, but can easily cause problems with Error carrying types like Fin (Fin<string> in particular). To avoid the confusion I have made it an explicit conversion operation.
Impact
Low
Mitigation
Manually cast to Error where needed - or call Error.New(string)
Before static interface methods existed, the technique was to rely on the non-nullable nature of structs to get access to 'static' methods (via interface based constraints), by calling default(TRAIT_TYPE).StaticMethod().
Language-ext has many of these 'trait types', like Eq<A>, HasCancel<A>, etc. They have all been updated to use static abstract methods.
Motivation
So, where before you might call: default(EqA).Equals(x, y) (where EqA is struct, Eq<A>) - you now need to call EqA.Equals(x, y) (where EqA is Eq<A>) .
This is obviously much more elegant and removes the need for the struct constraint.
Impact
Medium - your code will throw up lots of 'Cannot access static method' errors. It is a fairly mechanical processes to fix them up.
Mitigation
If you have implemented any of these traits, as instances, then you'll need to implement these changes:
struct from any constraints (where X : struct)static to trait method implementationsInst usages should be removedThe following types have all bee rewritten: Monad, Functor, Applicative, Alternative, Foldable, etc.
Motivation
The new static interfaces have opened up a more effective approach to higher-kinds in C#. Instead of doing as much as possible to retain the original types in methods like Bind, Map, Apply, etc. we now expect all types that need to leverage Monad, Functor, etc. to inherit K<M, A>.
For example, Option<A> inherits K<Option, A>, Seq<A> inehrits K<Seq, A>, Either<L, R> inherits K<Either<L>, R>. The M in K<M, A> is the trait implementation. So, Option (no generic argument) would inherit Monad<Option>, Traversable<Option>, etc.
Thise truly opens up higher-order generic programming in C#.
*** TODO: Insert a link here to a tutorial on higher-kinds in C# ***
Impact
High, if you have built your own Monad, Functor, Applicative implementations; or you have been writing code that leverages the generic nature of the traits. However, I doubt this impact will be large because the previous approach was cumbersome - hence the refactor.
Mitigation
This is rewrite territory. I would encourage you to look at the new traits and monad transformers - as they're much more effective.
Arithmetic trait: Plus renamed Add, Product renamed Multiply.
Semigroup<A> and Monoid<A> types have been refactoredThe Append in Semigroup<A> (which Monoid<A> inherits) is now an instance method. Meaning you must derive your semigroup and monoidal types from Monoid<YOUR_TYPE> to leverage its capabilities.
The functions in the TypeClass static class have been moved to: Monoid and Semigroup static module classes. TypeClass.mappend is now Semigroup.append, TypeClass.mconcat is now Monoid.concat, etc.
Semigroup also defines operator+ now.
Motivation
Monoids, like the other trait types, were set up work ad-hoc polymorphically. That is, we could build a Monoid instance for a type that we don't own. And, although we have now lost that capability, we have gained a much easier experience for working with monoidal types.
For example, Validation<MonoidFail, Fail, Success> is now just Validation<Fail, Success>. Previously, you'd have to specify the MonoidFail trait all the time because there was no way for C# to infer it. I suspect most people use the Validation<Fail, Success> variant with its built-in Seq of Fail results. Now it's just as easy to use any monoid.
This obviously means that, with types that you don't own, they can't be monoidal directly.
However, you can always wrap existing types with monoidal container:
public readonly record struct MEnumerable<A>(IEnumerable<A> Items) :
Monoid<MEnumerable<A>>
{
public MEnumerable<A> Append(MEnumerable<A> rhs) =>
new(Items.Concat(rhs.Items));
public static MEnumerable<A> Empty =>
new(Enumerable.Empty<A>());
}
This lifts an existing type into a monoid that you can then use with generic functions that expect a monoid. I think that although this is a little bit awkward, it's the scenario that happens then least; most of the time we have control over the type we want to be monoidal and so we can just inherit Monoid<YOUR_TYPE>.
Impact
Medium - I'm not expecting mass adoption of the previous traits system, so it probably will have a low impact for most. However, monoids were probably one of the easier traits to use.
Mitigation
Any implementations of Monoid<YOUR_TYPE> that you have, take the implementation and move the members into YOUR_TYPE and Append into a non-static method that takes a single argument rather than two (this is your first argument now).
TypeClass class has been renamed TraitLanguageExt.TypeClass is effectively a Prelude for the trait functions, this has been renamed to LanguageExt.Trait.
Motivation
The name type-class comes from Haskell, which has been a massive influence on this library, however, I think the word 'trait' is more descriptive than 'type class', which is potentially a bit confusing to the average C# developer.
Impact
Low
Mitigation
Search and replace TypeClass for Trait.
Apply extensions that use raw Func removedMotivation
The applicative-functor Apply function is supposed to work on lifted functions (i.e. M<Func<A, B>> not the raw Func<A, B>). I orignally provided variants that work with the raw Func for convenience, but really they're just Map by another name.
Impact
Medium
Mitigation
The new Functor trait gives all functor types a new variant of Map which takes the Func as the first argument and the functor value as the second (this differs from the existing handwritten Map methods which take this as the first argument and a Func as the second argument). That, along with new extension methods for Func<A, ..., J> have been added that curry the Func and then applies the map function.
So, instead of:
Func<string, string, string> surround =
(str, before, after) => $"{before} {str} {after}";
var mx = Some("Paul");
var my = Some("Mr.");
var mz = Some("Louth");
surround.Apply(mx).Apply(my).Apply(mz);
Change the first Apply to Map:
surround.Map(mx).Apply(my).Apply(mz);
Those of you that have used Haskell will recognise that pattern, as it's commonly used:
surround <$> mx <*> my <*> mz
Sequence extension methods have been removed (#1)The Sequence methods that follow this pattern have been removed:
public static Fin<Lst<B>> Sequence<A, B>(this Lst<A> ta, Func<A, Fin<B>> f) => ...
Motivation
Before being able to formulate the higher-kinds I had to manually write six Sequence methods and Traverse for every pair of monadic types. There are 450 of them in total. As you can imagine that's a real pain to develop and maintain.
The other issue is that Sequence and Traverse don't work for monadic types that you build. You have to write those extension methods yourself for every pair. This is hardly approachable.
There is a new trait type called Traversable that generalises traversals. The completely generic functions are available via:
Traversable.traverse(...)
Traversable.sequence(...)
Traversable.sequenceA(...)
Traversable.mapM(...)
They will work with any pair of traversble and applicatives.
I have also added a Traverse method to every traversable type (Seq, Option, etc.) and those will work with any applicative. Which means if you build your own monads/applicative-functors that have implementations that derive from Monad<M> and/or Applicative<F> then they will automatically work with the language-ext traversable types.
I have named the new version Traverse because that's really what it should have been called in the first place. So, as this is a big breaking changes release, I decided to bite the bullet and rename it.
Impact
High: Sequence is likely to be used a lot because it's so useful. Renaming it to Traverse will instantly cause your build to fail. The new version also returns a lifted value that you may need to convert.
Mitigation
Where your build fails due to Sequence you should change Sequence to Traverse and follow the call with .As() to lower the generic type:
Before:
var results = Seq(1,2,3).Sequence(x => x % 2 == 0 : Some(x) : None);
After:
var results = Seq(1,2,3).Traverse(x => x % 2 == 0 : Some(x) : None).As();
Sequence extension methods have been removed (#2)The Sequence methods that follow this pattern have been removed:
public static Option<Seq<A>> Sequence<A>(this Seq<Option<A>> ta) => ...
Motivation
The motivations are the same as for the other removal of Sequence. By removing it we get to use the generalised version which allows others to build monadic types that be composed with the language-ext types and be traversed.
The difference with this removal and the last one is that there is no equivalent Sequence method added to the monadic types (like Option, Seq, etc.); The reason for this is that it's not possible for C# to pick up the nested generics correctly, so it's a waste of time writing them.
You can still call Traversable.sequence and Traversable.sequenceA to do this manually, however it's much easier to call ma.Traverse(identity) which is isomorphic.
Mitigation
Before:
var results = Seq(Some(1), Some(2), None).Sequence();
After:
var results = Seq(Some(1), Some(2), None).Traverse(identity).As();
It's not quite as concise obviously, but it is completely generic and extensible which the previous one wasn't. You can also build your own extension methods for commonly used pairs of monads:
public static Option<Seq<A>> Sequence<A>(this Seq<Option<A>> mx) =>
mx.Traverse(identity).As();
That will restore the previous functionality.
Traverse extension methods have been removed (#3)The Traverse methods that follow this pattern have been removed:
public static Seq<Fin<B>> Traverse<A, B>(this Fin<Seq<A>> ma, Func<A, B> f) => ...
Motivation
The motivations are the same as for the other removals of Sequence. By removing it we get to use the generalised version which allows others to build monadic types that be composed with the language-ext types and be traversed.
Again, there will be no replacement for this as you can just use a combination of Sequence and Map to achieve the same goals.
Mitigation
Before:
var results = Seq(Some(1), Some(2), None).Traverse(x => x * 2);
After:
var results = Seq(Some(1), Some(2), None).Sequence().Map(x => x * 2).As();
Again, it's not quite as concise, but it is completely generic and extensible which the previous one wasn't. You can also build your own extension methods for commonly used pairs of monads:
public static Option<Seq<B>> Traverse<A, B>(this Seq<Option<A>> mx, Func<A, B> f) =>
mx.Sequence().Map(f).As();
That will restore the previous functionality.
ToComparer doesn't exist on the Ord<A> trait any moreMotivation
Because the trait types now use static methods, we can't now have a ToComparer() extension for the Ord<A> type. Instead there's a class called OrdComparer that contains a singleton IComparer property called Default.
Impact
Low
Mitigation
Use OrdComparer<OrdA, A>.Default instead of <OrdA>.ToComparer().
LanguageExt.ClassInstances.SumRenamed to LanguageExt.ClassInstances.Addition<SUM, A>
Mitigation
There's a new type called Sum<L, R> for use with transducers.
Impact
Low
Mitigation
Rename uses of Sum<NUM, A> to Addition<NUM, A>
Guard<E> has become Guard<E, A>The A is never used, this just allows guards to work in LINQ by enabling the implementation of SelectMany. The benefit is that a number of guards can be placed together in a LINQ statement, where only one could before.
Guard. If you only ever used the guard Prelude function this will have no impact.HasCancel<RT> should be replaced with HasIO<RT>The basic runtime traits needed by the effect monads (IO, Eff, and Aff) has been expanded to HasCancel<RT>, HasFromError<RT, E>, and HasSyncContext<RT>. These can all be applied manually or you can use the HasIO<RT, E> trait which wraps all three traits into a single trait.
Simply search and replace: HasCancel<RT> for HasIO<RT, Error>. This will work for all existing Eff and Aff code.
HasSyncContext<RT> allows all IO and Eff monads to use the post(effect) function. This allows the running of an effect on original SynchronizationContext (needed for WPF and Windows Form applications).HasFromErrror<RT, E> allows conversion of Error to E. The new IO monads have an parametric error type - and so this allows for exceptional errors and expecteded Error values to be converted to the parametric error type: E.UnitsOfMeasaure namespace converted to a static classThe various utilty fields used for units (seconds, cm, kg, etc.) have been moved out of the LanguageExt.Prelude and into LanguageExt.UnitsOfMeasure static class. The unit types (Length, Mass, etc.) are now all in the LanguageExt namespace.
using LanguageExt.UnitsOfMeasure should be converted to using static LanguageExt.UnitsOfMeasureusing static LanguageExt.UnitsOfMeasure either globally or in each cs file.Either doesn't support IEnumerable<EitherData> any moreThis is part of preparing the library for future serialisation improvements.
Either to an IEnumerable that supportsEither 'bi' functions have their arguments flippedThere were lots of methods like BiMap, BiBind, BiFold etc. where Left and Right were in the wrong order in the method argument list. So, I have flipped them.
These just keep causing problems with resolution of common functions like Map, Bind, etc.
The nullable Prelude remains.
Tuple and KeyValuePair removedValueTuple support only from now on.
Originally, I was going to just mark these as [Obsolete]. And, for a while, they were. But after over 1000 files changed with over 100,000 lines of code added or modified, I realised that maintaining these types (updating them to support nullable references and other fixups) was potentially doubling the effort I'd done up to that point. So, I have just deleted them outright. It is brutal, but it saved my sanity.
I am very, very, very sorry that this will mean you have to fixup everything before you can work with language-ext v5. But unfortunately, I have to spread the load of this, as it was burning me to the ground!
This is my '.NET Framework to .NET Core' moment. I realise that. And I an truly sorry to those that have to do the migration. Please make sure you have adequate time set aside for the migration.
Some<A>* Mitigtation: use nullable references instead
OptionUnsafe<A>* Mitigtation: use `Option<A?>` instead
OptionAsync<A>* Mitigtation: use `OptionT<IO, A>` instead
OptionNone:* Mitigtation: use `Fail<Unit>` instead
EitherUnsafe<L, R>:* Mitigtation: use `Either<L?, R?>` instead
EitherLeft<L>* Mitigtation: use `Fail<L>` instead
EitherRight<L>:* Mitigtation: use `Pure<R>` instead
EitherAsync<L, R>:* Mitigtation: use `EitherT<L, IO, R>` instead
Try<A>* Mitigtation: use `Eff<A>`
TryOption<A>* Mitigtation: use `OptionT<IO, A>`
TryAsync<A>* Mitigtation: use `Eff<A>`
TryOptionAsync<A>* Mitigtation: use `OptionT<IO, A>`
Result<A>* Mitigtation: use `Fin<A>`
OptionalResult<A>* Mitigtation: use `Fin<A?>`
Option<A> and Either<L, R>* Mitigtation: use `ToIO()` to convert to the transformer variants with embedded `IO`
ExceptionMatch, ExceptionMatchAsync, ExceptionMatchOptionalAsync* Mitigtations:
* use effect monads with `@catch`
* use `switch` expressions
LanguageExt.SysXthis was only needed to partition newer .NET Core code from .NET Standard. This has now been merged into LanguageExt.Sys
LanguageExt.CodeGenDeprecated from now. To be replaced later by LanguageExt.SourceGen. Note, this library has always been standalone and can therefore continue to work without new versions being released.
LanguageExt.TransformersNo need for them now we have proper higher-kind support.
bracketEitherT, TryT (derives EitherT<M, Error, A>), Try (derives TryT<Identity, A>)yieldAll, many, and repeat for Pipes needs tail recursion support
yieldAll, many have been temporarily removedyieldAll in Pipes has a temporary solution - need proper recursion strategy5.0.0-alpha.1, 5.0.0-alpha.2, etc. So we can release 5.0.0 when doneCount(), Head(), Last() etc.)Prelude_CollectionsTraverseM for each data-type (like the Traverse implementations)