A Container is IEnumerable<T> or Either<T1,T2> or loosely any type that contains other things...
The Select() and SelectMany() functions in Linq all extract/iterate an item-at-a-time from the provided container before proceeding further.
It runs a user provided function on it each extracted item and puts the result into a new container. If no items, it puts nothing into the container and a empty container is produced
The function is either a non-container producing function, usually named map() or a container-producing function usually called bind(). Both functions transform their input so effectively apart from how they return it, they do the same type of job - transformation.
The result of the scalar function is put into a new container either automatically by the Select/Map() functions for explicitly if you use Bind().
The result of the monad-producing function ie bind() is further extracted and that's put in the new container. If no result, nothing or None/Bad/ ndicating value put int Container
Short story:
-
Extract item from container,
-
Inspect the item to see it exists or is valid
-
If so(valid):
-
if scalar function, execute scalar function and return scalar result into new container,
-
if monad-producing function ie bind(), execute it and if any items in continer extract items from container and put into new container.
-
-
An item extracted from an IEnumerable<T> is extracted via the foreach Operator.
-
An item extracted from an Option<T> is extracted via Match() or Linq Select synatax
Eg:
Bind function attempts to run a monad-producing function on each item it can extract from the container. If it cannot extract the item, it doesn't run the function. Each item is extracted from that monad-producing function ie bind and returned.
public static Option<TOUT> Bind<TIN,TOUT>( this Option<TIN> self, // Container
Func<TIN, Option<OUT> bind // Container producing function
)
=> self.Match( // A) extract item from container
// and check its validity...
Some: a => bind(a), // B) If item valid (in this case just being
// a SOME makes it valid), run container-producing function
// bind() on item which will return another Container
None: () => None // If could not extract item or the extracted item
// is not valid, return Container of None
)
For Either<L,R>:
/* Bind runs a container producing function */
public static Either<L,R2> Bind<L, R1>( this Either<L,R1> self, // Takes in a container of Either<L, R1>
Func<R1, Either<L,R2> bind // Takes a container producing function
)
=> self.Match( // extract item from container
// and verify item
Right: r => bind(r), // if its a right item, its OK, run the container-producing function
// that in turn returns a container for next bind() if there is one...
Left: () => Left // if its a left, don't do container-producing function, just return left again..
)
Each time a Bind() encounters a Left in the container, it returns a left. If the container, in this case, is an instance of Either<Left, Right> but contains a left value:
result = Container.Bind(me_not_run()) // Container determined to not have right(correct), so return LEFT
Container.Bind(me_not_run_either()) // Container determined to be left(wrong), so return LEFT
Container.Bind(me_not_run_neither()) // Container determined to be left(wrong), so return LEFT
And Consequently never runs the user container producing function in this case…the resulting container is just a container with a Left in it that cascades all the way down and ultimately the result is a Container of Left.
Container1
// Attempt to extract item from Container1 and run container-producing function
// on it. Return the extracted item from container or
// indicate NotFound be returning Left
.Bind( item => { /* container producing function which return Container.
internally if extracted, returns each member of container otherwise Returns
Container<None/Left> /* }
.Bind( item => { ret Container} ) // Else Container<None/Left/Blank>
// If Has Item Of required (any Right)
.Bind( item => { do something on it and return Container} // else Container<None/Left>
// Whats hidden if HOW we extract the item, and what container to return
// if the item was not found or correct. Otherwise just return items
// from container produced from container-producing function
Bind's Story:
If the Container has the required value(or simple criteria of any specific value) then Get it and run a container-producing function on it.
If the Container doesn't, don't run the function and return a Blank/NotFound Container.
Plan your conditional code like this:
if(ContainerHasOKItem) {
// Your Ok Code which works with the OK Item and produces another Container
// for next Bind() to interrogate for OKness.
} else {
// Internal/Hidden code returns Container<BAD> for next Bind() to integorate for OKness.
}
which corresponds to the functional notation of:
Container.Bind(item=> me) // if my container has a valid item or state, run me()
// on the item or state. me() will return a container
.Bind(item => me) // this is the next bind which will re-check the
// container for a valid item or state etc..
.Bind(item => me)
// Internally Bind() checks if the container has Good or Bad value in it,if it has
// bad it returns Container<Bad> otherwise if returns your
// container producing function (which could container a good or
// bad value based on if your function said it should)
ContainerA
.Bind(containerA_has_ok_so_run_my_ok_func) // Produces a Container, Bind() then
// inspects it - returns Container<Bad> or passed Good to my_func
.Bind(containerA_has_ok_so_run_my_ok_func2) // Produces a Container ...
.Bind(containerA_has_ok_so_run_my_ok_func3) // Produces a Container ...
Note: The determination of what is a required value for the container to have, for the monad-producing() function to be called is within specified/coded within the Bind extension method.
So with all this being said the underlying ideas are essentially this:
-
Extract an item from the container
-
Verify the extracted item or state of container is suitable and run monad-producing function on it.
-
If the container or the item extracted from it is undesirable then return Container indicating bad item/state.
Something like this hopefully demonstrates it:
How much does your brain hurt now?
Side notes:
What makes a Monad a Monad?
Option<T> captures if behavior IEnumerable<T> captures foreach behavior Select/Map make types into functors Functors allow you to use regular functions(Fn()) with types that contain or amplify other types (T<A>): T<A>.Map(Fn()) Map is a Select which is a way to remove the boilder place code of a foreach Bind is a SelectMany without the project function. Bind() is a function that does the binding of one Monad to another