Releases: louthy/language-ext
Language-Ext Version 1.5 release
This is a major new release of Language-Ext with myriad new features and improvements. Much of this release is about refining and standardising the API. This is the first stable release of the LanguageExt.Process system (Actor system), LanguageExt.Process.FSharp (F# API for LanguageExt.Process), LanguageExt.ProcessJS (Javascript Actor system that allows seamless actor messaging from server to client), and LanguageExt.Process.Redis (for intra-system messaging and message/state persistence).
Standardisation comes in the form:
- Making all constructor functions start with a capital letter
- Making all fold and scan functions use the same fold function signature
- Creating a common set of functions that all of the monad types implement
So:
tuplebecomesTuplelistbecomesListmapbecomesMapconsbecomesCons- etc.
This makes the constructor functions consistent with Some, None, Left, Right, ... The only exception to this is the unit constructor, which stays as-is.
We also bring in a concept of a type of a 'higher kinded type' for all of the monadic types. It's not quite as effective or refined as a Haskell HKT, but it does create a standard interface (through extension methods) for all of the monadic types. The standard interface includes the following functions:
SumCountBindExistsFilterFoldForAllIterMapLift/LiftUnsafeSelectManySelectWhere
Because of this standardised interface, it's also possible to use monad transformer functions (for up to a two-level deep generic monad-type):
SumTBindTCountTExistsTFilterTFoldTForAllTIterTMapT
i.e.
var list = List(Some(1), None, Some(2), None, Some(3)); // Lst<Option<int>>
var presum = list.SumT(); // 6
list = list.MapT( x => x * 2 );
var postsum = list.SumT(); // 12New types
Map<K,V>- Replacement forImmutableDictionary<K,V>that uses an AVL tree implementationLst<T>- Wrapper forImmutableList<T>Set<T>- Wrapper forImmutableSet<T>Try<T>- LikeTryOption<T>but without the optional return valueRWS<E,W,S>- Reader/Writer/State monadExceptionMatch- Used for newExceptionextension methodMatchfor pattern matching on exception types.ActionObservable<T>- Used byIObservable<T>extension methodPostSubscribe. Allows an action to be executed post-subscription.
New functions
- static Process functions in
LanguageExt.Process par- Partial applicationcurry- Curryingrandom- Thread safe random number generatorifSomeadded forTryOption- dispatches an action if theTryOptionis in aSomestate
Improvements
- Improved
Unittype - ImplementedIEquatable<Unit>and equality operator overloads - Improved
IComparableandIEquatablesupport forOptionandEithertypes
Breaking changes
- Standardised the
foldandscandelegates toFunc<S,T,S>
Deprecated (made obsolete)
tuple- becomesTuplequery- becomesQuerymap- becomesMaplist- becomesListarray- becomesArraystack- becomesStackfailure- becomesifNone/ifLeftPrelude.empty- becomesList.empty/Set.empty/Map.emptycons- becomesConsrange- becomesRangewith- becomesmap
Nuget:
Language-ext 1.0.0
Version 1.0.0
Now that System.Collections.Immutable has been fully released (as version 1.1.36) we can now do a full release of Language-Ext.
As usual you can find the package on NuGet
Language-ext
Use and abuse the features of C#, which, if you squint, can look like extensions to the language itself. This package is a functional 'toolkit' and also solves some of the annoyances with C#, namely:
- Poor tuple support
- Null reference problem
- Lack of lambda and expression inference
- Void isn't a real type
- Mutable lists, dictionaries, sets, queues, etc.
- The awful 'out' parameter
- Common core functional types missing (
Option,Either,Unit, ...)
The library very heavily focusses on correctness, to give you the tools needed to write safe declarative code.
Features:
Powerful 'prelude' which you include by using static LanguageExt.Prelude (in C# 6) that covers many of the basic functional language core library functions and types (from using LanguageExt):
- Pattern matching
- Lambda type-inference:
var fn = fun( (int x, int y) => x + y ); Option<T>,OptionUnsafe<T>,Either<L,R>,EitherUnsafe<L,R>andTryOption<T>monads (probably the most complete implementations you'll find in the .NET world)tuple(a,b,...)-Tupleconstruction without typingTuple.Create(a,b,...)as well asmapto project theItem1..ItemNproperties onto named values.List- immutable listMap- immutable mapSet- immutable setmemo- Memoization with auto-cache purging using weak-referencesWritermonadReadermonadStatemonad- Extension methods and replacement functions for dealing with
out(Int32.TryParse,IDictionary.TryGetValue, etc.)
This only skims the surface of the library
Release notes
Additions:
- New
Reader<E,T>monadPrelude.Readerconstructor functionPrelude.askfunctionPrelude.localfunction
- New
Writer<W,T>monadPrelude.Writerconstructor functionPrelude.tellfunction
- New
State<S,T>monadPrelude.Stateconstructor functionPrelude.getfunctionPrelude.putfunction
Option<T>IfSomemethod for dispatching actions and ignoringNonePrelude.ifSomeas aboveIfNonemethod (replacesFailure)Prelude.ifNonefunction (replacesPrelude.failure)ToEitherconverts anOption<T>to anEither<L,R>(you must provide a default(L) value or func incase theOptionis in aNonestate)ToEitherUnsafeconverts anOption<T>to anEitherUnsafe<L,R>(you must provide a default(L) value or func incase the Option is in a None state)Somefluent method now also supportsAction
*OptionUnsafe<T>IfSomeUnsafemethod for dispatching actions and ignoringNonePrelude.ifSomeUnsafeas aboveIfNoneUnsafemethod (replacesFailureUnsafe)Prelude.ifNoneUnsafefunction (replacesPrelude.failureUnsafe)ToEitherUnsafeconverts anOptionUnsafe<T>to anEitherUnsafe<L,R>(you must provide a default(L) value or func incase the OptionUnsafe is in a None state)Somefluent method now also supportsAction
TryOption<T>IfSomemethod for dispatching actions and ignoringNoneorFailPrelude.ifSomeas aboveIfNonemethod (replacesFailure)Prelude.ifNonefunction (replacesPrelude.failure)IfNoneOrFailmethod for handling both failure states separately (Some state uses identity function)Prelude.ifNoneOrFailas aboveTryOptionConfig.ErrorLoggerstatic variable which can be used to attach error logging behaviour to theFailstate ofTryOptionPrelude.tryfunfunction wraps aTryOptionin aFuncToOptionconverts aTryOption<T>to aOption<T>(Fail becomes None)Somefluent method now also supportsAction
Either<L,R>IfRightmethod for dispatching actions and ignoringLeftPrelude.ifRightas aboveIfLeftmethod (replacesFailure)Prelude.ifLeftmethod (replacesPrelude.failure)Rightfluent method now also supportsActionToOptionconverts anEither<L,R>to anOption<R>(LbecomesNone)ToTryOptionconverts anEither<L,R>to aTryOption<R>ToEitherUnsafeconverts anEither<L,R>to anEitherUnsafe<L,R>(LbecomesNone)
EitherUnsafe<L,R>IfRightUnsafemethod for dispatching actions and ignoringLeftPrelude.ifRightUnsafeas aboveIfLeftUnsafemethod (replacesFailureUnsafe)Prelude.ifLeftUnsafemethod (replacesPrelude.failureUnsafe)Rightfluent method now also supportsAction
Updates:
Prelude.convert<T>now returnsNoneif input isnull(it previously threw an exception)
Fixes:
- Query.zip would go into an infinite loop. Fixed.
- Comments
Deprecated:
- Dependency on
ConcurrentHashTable failureandFailure(forifNone,IfNone,ifLeft, etc.)Iterextension method inQuery, it was causing resolution problems for the compiler.- Removed
RightUnsafeandLeftUnsafefromEither, these were a hangover from whenEitherUnsafedidn't exist andEitherhad a dual role. This isn't needed any more.
Queryable and TypeConvertor support
Deprecated headSafe
Added headOrNone for safely finding the optional head item in a collection
Added Query namespace (static class) for working with IQueryable. Has functions:
headOrNonetailmapfilterchoosecollectsumrevappendfoldfoldBackreducereduceBackfindfreezeziplengthiterforalldistincttaketakeWhileexists
OptionandSomeTypeConverter support
Either RL to LR flip - Breaking change!
Either<R,L>becomes:
Either<L,R>Warning: if you're currently using Either then getting the latest will break your compilation. You will need to update all usages of Either<R,L> to Either<L,R>, which is potentially tedious for large projects. Please be warned.
Normally I wouldn't do such a big breaking change. But I have finally been convinced that this is the correct way and I think it's best to just bite the bullet and get it done. Apologies if this causes headaches, I promise it won't happen again!
Latest on nuget: https://www.nuget.org/packages/LanguageExt/
API refinement and bug fixes
Operators
None and Left coalescing using the logical-or operator.
This means chaining of optional values with defaults:
Option<int> optional1 = None;
Option<int> optional2 = None;
var res = optional1 || optional2 || "Default";
// Some("Default")This also means you can use them in LINQ expressions where from is logical-AND and || is logical-OR:
Option<int> optional1 = None;
Option<int> optional2 = Some(10);
Option<int> optional3 = Some(20);
var res = from x in optional1 || optional2
from y in optional3
select x + y;
// Some(30)true and false operators implemented for option and either types.
var optional = Some(123);
if( optional )
{
// true
}== and != operators implemented for option and either types:
var optional = Some(123);
if( optional == 123 )
{
// true
}
Option<int> optional = None;
if( optional == None )
{
// true
}With
with(Tuple,..) and With(Tuple,..) are now map(Tuple,..) and Map(Tuple,..). They were poorly named, these are more consistent with their action. The with functions that take up to 5 parameters are also renamed map.
The old with functions have been marked [Obsolete]
Thanks to @tomaszpolanski for the feedback
#15
Optional
Added Optional, it will take T or null and return a Some(T) or None respectively. This is for where you want to be explicit about the conversion from T to Some(T)|None, and for when the type system complains at you (in the branches of a ternary operator for example):
string x = GetValueFromNonTrustedAPI();
return Optional(x);Thanks to @tejacques for the feedback:
#12
... and Various bug fixes
More list matching, Collections functions (List, Map, Set), Range improvements
List matching
Improved list matching. There are many more overrides for deconstructing the head items from a list (up to six elements). e.g.
// The fewest number of elements deconstructed
int Product(IEnumerable<int> list) =>
match(list,
() => 1,
(x, xs) => x * Product(xs));
// The most
int GetLength(IEnumerable<int> list) =>
match(
lst,
() => 0,
a => 1,
(a, b) => 2,
(a, b, c) => 3,
(a, b, c, d) => 4,
(a, b, c, d, e) => 5,
(a, b, c, d, e, f) => 6,
(x, xs) => xs.Count() + 1
);Also, a null list is considered empty.
range
Additional range() variants:
- Ranges with different step sizes -
range(0,100,10) - range of ranges -
range( range(0,100), range(200,100) ) - range of chars -
range('a','z')
List
Additional functions:
repeatinit- generate a sequence where each step calls a generator function with the indexinitInfinitechoosecollectscanscanBackfinddistincttaketakeWhileunfoldexists
Breaking changes
foldrrenamedfoldBack- Removed the variants of
mapanditer(mapianditeri) and just used overloading instead - Fluent variants now use Pascal Case naming
Map
Additional function:
exists
Breaking changes
containsrenamedcontainsKey- Fluent variants now use Pascal Case naming
Set
Additional functions:
addcomparelengthdifferenceexistsfilterintersectmapcontainsremoveisSubsetisProperSubset
Tuple
Breaking change
- Removed
thisfromwith, they shouldn't be extension methods becauseWithis already performing that duty.
Assorted updates
convertreturnsOption<T>- Added
TryGetValueforImmutableDictionaryandImmutableSet - Added
AsEnumerable()extension toNullable<T> - Functions for converting IEnumerable to immutable collections:
toList,toArray,toSet,toQueue,toStack
On NuGet now: https://www.nuget.org/packages/LanguageExt/
List pattern matching
List pattern matching:
public int Sum(IEnumerable<int> list) =>
match( list,
() => 0,
x => x,
(x, xs) => x + Sum(xs) );Extension methods for lifting Option, OptionUnsafe, Either, EitherUnsafe when wrapped by an IEnumerable.
TryOption<T>.AsEnumerable() now returns an IEnumerable<Either<T,Exception>> instead of burying the Exception as an empty list. The extension methods above can be used to easily extract the value from the IEnumerable<Either<T,Exception>>. This puts control back in the programmer's hands about what to do with the possible failed state of the TryOption.
e.g.
// Ignore all errors, and return an empty list
var res = (from v in GetTryOptionError().AsEnumerable().FailWithEmpty()
from r in range(1, 10)
select v * r);
// Ignore the errors and provide a sensible default
var res = (from v in GetTryOptionValue(false).AsEnumerable().Failure( list(1) )
from r in range(1, 10)
select v * r);
// Recognise the error, and throw
var res = (from v in match(
GetTryOptionValue(true).AsEnumerable(),
Right: r => list(r),
Left: ex => failwith<int>("broken: " + ex.Message)
)
from r in range(1, 10)
select v * r);Check LinqTests.cs and ListMatchingTests.cs for examples.
New functions and memoization improvements
New functions for Option, OptionUnsafe, TryOption:
- bind
- map
- exists
- fold
- count
- forall
- ToList
- ToArray
New functions in List and Map:
- iter
- forall
- mapi
- addRange
Improved memoization:
- Uses a WeakDictionary from the ch.codeplex.com library, this removes the possibility of memory-leaks whilst maintaining the compositional nature of
Func<T,R>. In a unit-test memoizing 0 -> Int32.MaxValue strings, the NUnit runner never jumped above 45mb. - memo() must use a reference type for R, so the old memoization function is kept for completeness, except it's now called memoUnsafe, with warnings in the comments about potential memory leaks.
Breaking changes:
- each() has been renamed iter()
- AsEnumerableOne() renamed AsEnumerable() and removed the infinite variants (originally called AsEnumerable())
TryOption and standalone 'Unsafe' variants
New TryOption<T> type for catching all three possible outputs of a function: value, null, Exception. With three branches in Match: Some|None|Fail.
Option<T> and Either<R,L> have been split in to Option<T>, OptionUnsafe<T>, Either<R,L> and EitherUnsafe<R,L>. The 'unsafe' variants are for the cases where null is actually a valid Some value. They are only 'unsafe' because the of this reason.
Construction of OptionUnsafe<T> and EitherUnsafe<R,L> is done using SomeUnsafe(value), RightUnsafe(value), LeftUnsafe(value). Matching is done using matchUnsafe(...) and failureUnsafe.
This is much more declarative, and will clearly bias the programmer toward the safe types, only opting out when they absolutely need null as a valid value.
Breaking change: set becomes setItem.
| New prelude functions | Description |
|---|---|
stack<T>() |
Create an IImmutableStack |
set<T>() |
Create an IImmutableHashSet |
array<T>() |
Create an ImmutableArray |
queue<T>() |
Create an IImmutableQueue |
| New List functions | Description |
|---|---|
| freeze | Takes an IEnumerable and turns it into an IImmutableList |
| zip | Wrapper for Enumerable.Zip |
| length | Wrapper for IImmutableList.Count() |
| New Map functions | Description |
|---|---|
| length | Wrapper for IImmutableDictionary.Count() |
First batch of feedback release
Option and Either updates
Some(null)now doesn't coerce to aNone, aValueIsNullExceptionwill be thrownSome(Nullable<T>)supported, will coerce theValuetoSomeifHasValueistrue, otherwise aValueIsNullExceptionwill be thrownRight(Nullable<T>)/Left(Nullable<T>)supported, will coerce theValuetoRight/LeftifHasValueis true, otherwise aValueIsNullExceptionwill be thrownSomeUnsafe()added : AllowsSome(null)and allows theSomeandNonebranches ofmatchto returnnull.RightUnsafe()/LeftUnsafe()added : AllowsRight(null)andLeft(null)and allows theRightandLeftbranches of match to returnnull.- Added
IsUnsafetoOption<T>andEither<R,L>
I'm considering whether the 'unsafe' variants should return a OptionUnsafe<T> and EitherUnsafe<R,L> to make it explicit that the Some,Right and Left branches could have null and that's a valid result. I'm inviting feedback...