Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork446
C# pure functional programming framework - come and get declarative!
License
louthy/language-ext
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This library uses and abuses the features of C# to provide a pure functional-programming framework that, if you squint, can look likeextensions to the language itself. The desire here is to make programming in C# much more robust by helping the engineer's inertia flowin the direction of declarative and pure functional code rather than imperative. Using these techniques for large code-bases can bringtangible benefits to long-term maintenance by removing hidden complexity and by easing the engineer's and team's cognitive load.
Author on...
- Blog:Notes from a Small Functional Island
- Bluesky:@paullouth.bsky.social
- Mastodon:@louthy@4four.org
- Github ReadME project:'Functional programming is finally going mainstream'
| Nu-get package | Description |
|---|---|
| LanguageExt.Parsec | Port of theHaskell parsec library |
| LanguageExt.Streaming | A set of compositional streaming types |
| LanguageExt.FSharp | F# to C# interop package. Provides interop between the LanguageExt.Core types (likeOption,List andMap) to the F# equivalents, as well as interop between core BCL types and F# |
| LanguageExt.Parsec | Port of theHaskell parsec library |
| LanguageExt.Rx | Reactive Extensions support for various types within the Core |
| LanguageExt.Sys | Provides an effects wrapper around the .NET System namespace making common IO operations pure and unit-testable |
To use this library, simply includeLanguageExt.Core.dll in your project or grab it from NuGet. It is also worth setting up someglobal using for your project. This is the full list that will cover all functionality and bring it into scope:
globalusingLanguageExt;globalusingLanguageExt.Common;globalusingLanguageExt.Traits;globalusingLanguageExt.Effects;globalusingLanguageExt.Streaming;globalusingLanguageExt.Pretty;globalusingLanguageExt.Traits.Domain;globalusingstaticLanguageExt.Prelude;
A minimum, might be:
globalusingLanguageExt;globalusingstaticLanguageExt.Prelude;
The namespaceLanguageExt contains most of the core types;LanguageExt.Prelude contains the functions that bring into scope the prelude functions that behave like standalone functions in ML style functional programming languages;LanguageExt.Traits brings in the higher-kinded trait-types and many extensions;LanguageExt.Common brings in theError type and predefinedErrors.
From C# 6 onwards we got the ability to treat static classes like namespaces. This means that we can use staticmethods without qualifying them first. That instantly gives us access to single term method names that look exactly like functionsin ML-style functional languages. i.e.
usingstaticSystem.Console;WriteLine("Hello, World");
This library tries to bring some of the functional world into C#. It won't always sit well with the seasoned C# OO programmer,especially the choice ofcamelCase names for a lot of functions and the seeming 'globalness' of a lot of the library.
I can understand that much of this library is non-idiomatic, but when you think of the journey C# has been on, is "idiomatic"necessarily right? A lot of C#'s idioms are inherited from Java and C# 1.0. Since then we've had generics, closures, Func, LINQ,async... C# as a language is becoming more and more like a functional language on every release. In fact, the bulk of the newfeatures are either inspired by or directly taken from features in functional languages. So perhaps it's time to move the C#idioms closer to the functional world's idioms?
My goal with this library is very much to create a whole new community within the larger C# community. This community is notconstrained by the dogma of the past or by the norms of C#. It understands that the OOP approach to programming has some problemsand tries to address them head-on.
And for those that say "just use F#" or "just use Haskell", sure, go do that. But it's important to remember that C# has a lotgoing for it:
- Incredible investment into a state-of-the art compiler
- Incredible tooling (Visual Studio and Rider)
- A large ecosystem of open-source libraries
- A large community of developers already using it
- This is also very important for companies that hire engineers
- Itis a functional programming language! It has first-class functions, lambdas, etc.
- And with this library it has a functional-firstBase Class Library
One of the areas that's likely to get seasoned C# heads worked up is my choice of naming style. The intent is to try and makesomething thatfeels like a functional language rather than following rules of naming conventions (mostly set out bythe BCL).
There is, however, a naming guide that will keep you in good stead while reading through this documentation:
- Type names are
PascalCasein the normal way - The types all have constructor functions rather than public constructors that you instantiate with
new. They will alwaysbePascalCase:
Option<int>x=Some(123);Option<int>y=None;Seq<int>items=Seq(1,2,3,4,5);List<int>items=List(1,2,3,4,5);HashMap<int,string>dict=HashMap((1,"Hello"),(2,"World"));Map<int,string>dict=Map((1,"Hello"),(2,"World"));
- Any (non-type constructor) static function that can be used on its own by
using static LanguageExt.PreludearecamelCase.
varx=map(opt, v=>v*2);
- Any extension methods, or anything "fluent" are
PascalCasein the normal way
varx=opt.Map(v=>v*2);
Even if you disagree with this non-idiomatic approach, all of thecamelCase static functions have fluent variants, so you never actually have to see the non-standard stuff.
| Location | Feature | Description |
|---|---|---|
Core | IO<A> | A synchronous and asynchronous side-effect: an IO monad |
Core | Eff<A> | A synchronous and asynchronous side-effect with error handling |
Core | Eff<RT, A> | Same asEff<A> but with an injectable runtime for dependency-injection: a unit testable IO monad |
| Location | Feature | Description |
|---|---|---|
Core | Atom<A> | A lock-free atomically mutable reference for working with shared state |
Core | Ref<A> | An atomic reference to be used in the transactional memory system |
Core | AtomHashMap<K, V> | An immutableHashMap with a lock-free atomically mutable reference |
Core | AtomSeq<A> | An immutableSeq with a lock-free atomically mutable reference |
Core | VectorClock<A> | Understand distributed causality |
Core | VersionVector<A> | A vector clock with some versioned data |
Core | VersionHashMap <ConflictV, K, V> | Distrubuted atomic versioning of keys in a hash-map |
| Location | Feature | Description |
|---|---|---|
Core | Arr<A> | Immutable array |
Core | Seq<A> | Lazy immutable list, evaluate at-most-once - very, very fast! |
Core | Iterable<A> | Wrapper aroundIEnumerable with support for traits - enables the higher-kinded traits to work with enumerables. |
Core | Lst<A> | Immutable list - useSeq overLst unless you needInsertAt |
Core | Map<K, V> | Immutable map |
Core | Map<OrdK, K, V> | Immutable map with Ord constraint onK |
Core | HashMap<K, V> | Immutable hash-map |
Core | HashMap<EqK, K, V> | Immutable hash-map with Eq constraint onK |
Core | Set<A> | Immutable set |
Core | Set<OrdA, A> | Immutable set with Ord constraint onA |
Core | HashSet<A> | Immutable hash-set |
Core | HashSet<EqA, A> | Immutable hash-set with Eq constraint onA |
Core | Que<A> | Immutable queue |
Core | Stck<A> | Immutable stack |
| Location | Feature | Description |
|---|---|---|
Core | Option<A> | Option monad |
Core | OptionT<M, A> | Option monad-transformer |
Core | Either<L,R> | Right/Left choice monad |
Core | EitherT<L, M, R> | Right/Left choice monad-transformer |
Core | Fin<A> | Error handling monad, likeEither<Error, A> |
Core | FinT<M, A> | Error handling monad-transformer |
Core | Try<A> | Exception handling monad |
Core | TryT<M, A> | Exception handling monad-transformer |
Core | Validation<FAIL ,SUCCESS> | Validation applicative and monad for collecting multiple errors before aborting an operation |
Core | ValidationT<FAIL, M, SUCCESS> | Validation applicative and monad-transformer |
| Location | Feature | Description |
|---|---|---|
Core | Reader<E, A> | Reader monad |
Core | ReaderT<E, M, A> | Reader monad-transformer |
Core | Writer<W, A> | Writer monad that logs to aW constrained to be a Monoid |
Core | WriterT<W, M, A> | Writer monad-transformer |
Core | State<S, A> | State monad |
Core | StateT<S, M, A> | State monad-transformer |
| Location | Feature | Description |
|---|---|---|
Parsec | Parser<A> | String parser monad and full parser combinators library |
Parsec | Parser<I, O> | Parser monad that can work with any input stream type |
| Location | Feature | Description |
|---|---|---|
Core | Doc<A> | Produce nicely formatted text with smart layouts |
| Location | Feature | Description |
|---|---|---|
Core | Patch<EqA, A> | Uses patch-theory to efficiently calculate the difference (Patch.diff(list1, list2)) between two collections ofA and build a patch which can be applied (Patch.apply(patch, list)) to one to make the other (think git diff). |
The traits are major feature ofv5+ language-ext that makes generic programming with higher-kinds a reality. Check out Paul'sseries on Higher Kinds to get a deeper insight.
These work a little like type-aliasing but they impart semantic meaning and some common operators for the underlying value.
| Location | Feature | Description |
|---|---|---|
Core | DomainType<SELF, REPR> | Provides a mapping fromSELF to an underlying representation:REPR |
Core | Identifier <SELF> | Identifiers (like IDs in databases:PersonId for example), they are equivalent toDomaintType with equality. |
Core | VectorSpace<SELF, SCALAR> | Scalable values; can add and subtract self, but can only multiply and divide by a scalar. Can also negate. |
Core | Amount <SELF, SCALAR> | Quantities, such as the amount of money in USD on a bank account or a file size in bytes. DerivesVectorSpace,IdentifierLike,DomainType, and is orderable (comparable). |
Core | Locus <SELF, DISTANCE, SCALAR> | Works with space-like structures. Spaces have absolute and relative distances. Has an origin/zero point and derivesDomainType,IdentifierLike,AmountLike andVectorSpace.DISTANCE must also be anAmountLike<SELF, REPR, SCALAR>. |
These features are still a little in-flux as of 17th Oct 2024 - they may evolve, be renamed, or removed - but I like the idea!
For some non-reference like documentation:
- Paul's blog:Notes from a Small Functional Island does deep dives into the philosophy of FP and the inner-workings of language-ext.
- The wiki has some additional documentation, some might be a little out of date since the big
v5refactor, but should give some good insights.
If you would like to get involved with this project, please first read theContribution Guidelines and theCode of Conduct.
About
C# pure functional programming framework - come and get declarative!
Topics
Resources
License
Code of conduct
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.

