The function map will transform some value in a context, potentially changing the type, but stay in the same context. Most languages doesn't have a way to talk about "some context", and will implement the function for each context, and the only similarity between them will be the name map as the language isn't able to express this function.

C# is such a language, but even if we're unable to create a generic interface that states "this context implements map", we can still implement a method with the same name which operates the same way.

In C#, we have IEnumerable<T> which is an interface that supports enumerating over the elements in a collection. The mapping function for this interface is implemented as an extension method, and is named Select rather than map .

IEnumerable < TResult > Select < TSource , TResult > ( this IEnumerable < TSource > source , Func < TSource , TResult > selector ) ;

This could have been called Map instead, but Microsoft implemented this as a part of the Language Integrated Query feature (iirc), and modeled it after SQL where it's called select and where rather than map and filter . I'll use the functional name and C#s name interchangeably, so if you wonder what I'm talking about, refer to this table

X also X Select map SelectMany bind ContinueWith bind andThen bind Where filter flatten join unwrap get

If C# supported the "some context" feature, the above code could look more like the following pseudo-C#

TMappable < TResult > Select < TMappable < TSource > , TMappable < TResult >> ( this TMappable < TSource > source , Func < TSource , TResult > selector ) ;

So we're unable to express this, but let's revisit what map actually does; it lets you transform something in some context, but stay in the same context. This is a feature we should be able to implement for various different contexts, and languages which supports this actually does so (it's called Functor in other languages).

One useful context I've implemented map for is Task . A Task is an asynchronous operation that you can start in the background and wait for the result when you need it. Often you'll also want to post-process the result before continuing as is the case when fetching data from a database or a web API. C# doesn't implement map on Task , but they have implemented a more powerful function called ContinueWith we can use to build our map .

This is our implementation of map . It will transform the value within the Task in the same way the operation with the same name does on a collection.

Task < TB > Select < TA , TB > ( this Task < TA > x , Func < TA , TB > f ) => x . ContinueWith ( t => { if ( t . IsFaulted ) { ExceptionDispatchInfo . Capture ( t . Exception . InnerException ) . Throw ( ) ; return default ; } else if ( t . IsCanceled ) { throw new OperationCanceledException ( ) ; } else { return f ( t . Result ) ; } } , TaskContinuationOptions . RunContinuationsAsynchronously ) ;

Well, this was a bit of an anti-climax :/ But fear not as we'll use our "new" functionality in order to implement another very useful operation called bind !

Let's look at another feature of IEnumerable called SelectMany . Much like Select , it will transform each value in the enumerable, but instead of returning a simple value, it returns the value wrapped in another enumerable. It will then flatten (also called join ) the result so we don't have IEnumerable<IEnumerable<TResult>> .

IEnumerable < TResult > SelectMany < TSource , TResult > ( this IEnumerable < TSource > source , Func < TSource , IEnumerable < TResult >> selector ) ;

But in the same way we wanted to generalize Select for TSomeContext , SelectMany could also have been generalized.

TSomeContext < TResult > SelectMany < TSomeContext < TSource > , TSomeContext < TResult >> ( this TSomeContext < TSource > source , Func < TSource , TSomeContext < TResult > selector ) ;

Let's look at an example where we usually could get a TSomeContext<TSomeContext<T>> .

Task < Json > fetch = FetchSomeWebStuff ( ) ; Json nextUrl = FetchNextUrl ( fetch . Result ) ; Task < Url > next = FetchFromNextPage ( nextUrl ) ; Result result = next . Result ;

We could wrap the specific things inside a new Task so we get all code specific to this process in one place and not interleaved with unrelated code.

var myProcess = Task . Run ( ( ) => { Task < Json > fetch = FetchSomeWebStuff ( ) ; Json nextUrl = FetchNextUrl ( fetch . Result ) ; Task < Url > next = FetchFromNextPage ( nextUrl ) ; Result result = next . Result ; return result ; } ) ; Result result = myProcess . Result ;

But if we squint a little, we see that we transform the output from the first, and then use this to create a new Task. This sounds a bit like map followed by join . And the combination of these two is called bind (or SelectMany ).

We start by creating a function to extract the value from the task. This already exists as Result , so we just wrap it for kicks.

T Unwrap < T > ( this Task < T > task ) { try { return task . Result ; } catch ( AggregateException ex ) { if ( ex . InnerExceptions . Count == 1 ) { ExceptionDispatchInfo . Capture ( ex . InnerException ) . Throw ( ) ; throw ; } throw ; } }

join / flatten will just remove one layer of the context.

Task < T > Join < T > ( this Task < Task < T >> x ) => x . Unwrap ( ) ;

And we can implement SelectMany in terms of these

Task < TB > SelectMany < TA , TB > ( this Task < TA > x , Func < TA , Task < TB >> f ) => x . Select ( f ) . Join ( ) ;

Our example thus becomes

var myProcess = FetchSomeWebStuff ( ) . Select ( FetchNextUrl ) . SelectMany ( FetchFromNextPage ) ; var result = myProcess . Result ;

Tada! We have shown that Select is the same as map and SelectMany is the same as bind , and we've implemented these for Task to allow composing operations the same way as we usually do for IEnumerable .

Now try to implement Select and SelectMany for Nullable<T> !

PS: Notice that we implemented map ( Select ) in terms of bind ( ContinueWith ). It is possible to implement map in terms of bind for anything which implements bind .