Lets talk about Language.Ext specifically and if you're interested in the languageExt tutorial go to The languageExt Tutorial
If you want to know a little about functional programming ideas generally, checkout
People who know Functional programming tend to throw around a lot of buzzwords like "Composing of functions", "Declarative code syntax", "Monads", "Functors", "Pure functions", "Immutability". The former I talked about in Pure functions, and the later, I talked about in Being a bit more functional
So lets talk about monads.
Monads are design patterns designed to prevent boilerplate code.
Enumerable<T> is a monad that removes the foreach boilerplate code. Monads can also eliminate the need to if..else imperative conditionals in your code but still achieve them. Monads can use Bind() to achieve this. I'll talk about how that looks like a bit later.
So what are monads? Well, they are just classes or objects really. Usually, they are likened to C++ STL containers in as much as they need to support a specific interface and have features that make working with any monads predictable. For now, you can assume a monad is just a type like a stack or a list that have the following distinct features:
Monads are objects that:
-
Have a Select() extension method extracts an item from the Monad and allows a function to run for each item in the monad. That function happens to be called a mapping function (a value producing function). You can safely assume that a monad is a container of items or just one item and you need to access the elements of a container. A mapping function allows you to not only access the items but act on each item or transform it someway). This is what a user-provided mapping function will do for a monad to which its used on. By applying a function that runs against each item in the monad and turns in into something else.
-
Have a SelectMany()-like extension method (its called Bind() ) which extracts an item from the Monad that allows a monad producing a function to be run for each item in the monad. That function happens to be called a binding function (a monad producing function)
-
This is not SelectMany() and described in Understanding how the Linq query syntax works but similar as it doesn't actually call any project() and its called Bind()!
-
- Have the following functions defined for the type:
-
Sum // For Option<int> it's the wrapped value. Count // For Option<T> is always 1 for Some and 0 for None. Bind // Part of the definition of anything monadic - SelectMany in LINQ Exists // Any in LINQ - true if any element fits a predicate Filter // Where in LINQ Fold // Aggregate in LINQ ForAll // All in LINQ - true if all element(s) fits a predicate Iter // Passes the wrapped value(s) to an Action delegate Map // Part of the definition of any 'functor'. Select in LINQ Lift / LiftUnsafe // Different meaning to Haskell, this returns the wrapped value. Dangerous, should be used sparingly. Select SeletMany Where
It must just be quickly be said, that the Select() function on a Monad or on an IEnumerable<T> fulfils the same functionality as the foreach operator and as such removes the need to use it and thus removes that particular piece of boilerplate code. The IF conditional will be replaced by the Bind() operation on Monads but I'm getting ahead of myself.
This enables Linq syntax (which is usable only by types that have a Select() and SellectMany()) to work on monads (Monads do have a SelectMany() and a Bind() also).
To understand how Select() and SelectMany() works checkout Understanding how the Linq query syntax works. There i describe how IEnumerable has Select() and SelectMany().
This suggests that you can enable any types (much as monads) to be used in LINQ expressions if they have a Select() and a SelectMany() like I've done here
But while your types can be used in LINQ expressions these types are not yet Monads until they also have Return() and Bind() extension methods (which are also similar to their Select and SelectMany counterparts):
-
Map() extension method, like Select extension method, extracts the item and runs a provided mapping function on it, however, it will put the transformation into a monad
-
Bind() extension method, like the SelectMany extension method, extracts the item and passes it to a binding function(monad producing function) without the project() function that SelectMany() performs. The transfomation function needs to return a monad ie put the transformation into a monad.
This is how Linq is used, just it with an IEnumerable<T> to deal with a Monad called an Option<T> in language.ext:
using static LanguageExt.Prelude;
Option<string> optionA = Some("Hello, ");
Option optionB = Some("World");
Option optionNone = None;
var result1 = from x in optionA
from y in optionB
select x + y;
var result2 = from x in optionA
from y in optionB
from z in optionNone
select x + y + z;
So this code actually uses LINQ to get inside a monad, like for-each gets an item from within an enumerable.
In many cases, this (the value extracted from the IEnumerable or Monad) is either their or it isn't - obviously if its there, then you can use it - if its not, nothing that would have used it ever uses it (more on this idea later)...
So this is how it works (monads generally) when using Linq syntax specifically (you'll see this is exactly like how you used it with an IEnumerable):
from a in ma
This is saying “Get the value, a out of the monad ma”. For IEnumerable that means get the values from the say List<T>,
for Option<T> it means get the value if it's not in a None state. (More on what this means later) - This is validation.
As before, the following is saying “Get the value a out of monad ma, and then if we have a value get the value b out of monad mb”. So for IEnumerable if ma is an empty collection then the second from won't run - see more about the validation in the implementation of Map and Bind()
For Option<T> if ma is a None then the second from won't run.
x = from a in ma
from b in mb
select a + b;
select in monad land, specifically above, means automatically put this value back into a new monad x
Select()and SelectMany() are extension methods that loop through the items and then put them back into the list.
So implementing a Select and a SelectMany makes it possible to use :
from a in ma
where there exists an extension method for ma called Select() as described in Understanding Select and SelectMany Implementation which is useful to look at if you dont quite understand the following conversation...
So this states that a Select invocation on an IEnumerable will run the map function on each item on the enumerable and place it back as an enumerable. In this way, map() says it will run a function on each item on the enumerable, a map function. Map() is run on every value in the enumerable
Also, a SelectMany invocation on an IEnumerable will run the bind function(which results in a IEnumerable) on each item in the list. Each item in the resulting list run against a project() function. See tutorial 01
So a bind function(for an IEnumerable) is run to transform the extracted item, and then derived from the input as each item in the IEnumerable, each item in the subsequnt list is evaluated by a project function which used the top level looping item in the enumerable that was used to produce the secondary list.
Select allows this to work:
from a in ma
select a;
SelectMany allows this to work:
from a in ma
from b in mb
select a + b;