PAGES

7

Aug 13

Thinking Functionally in C# with monads.net



Functional concepts have worked their way into C# most notably through linq and lambda expressions, but the benefits of functional concepts in C# don’t have to stop there. This post will exemplify how functional concepts can be used to manage boilerplate code, such as if/else statements, null checks, and logging. The examples below are written using extension methods from the GitHub project, monads.net. monads.net isn’t required or even endorsed for working more functional goodness into your C# code, but it’s a great tool to start learning and sparking ideas with.

Before we dive into the code pit, please read my painless, oversimplified explanation of what monads are:
Monads are utility functions that accept and conditionally pass the result of functionX() to functionY().

Let’s start by reaching into a deeply nested object structure while doing null checks along the way.

C#

		Building building = SomeQueryThatShouldReturnABuilding();
		string phoneNumber = null;

		if(building != null)
		{
			if(building.Manager != null)
			{
				if(building.Manager.ContactInfo != null)
				{
					if(building.Manager.ContactInfo.PhoneNumber != null)
					{
						phoneNumber = building.Manager.ContactInfo.PhoneNumber;
					}
				}
			}
		}
	

C# with monads.net

	string phoneNumber = SomeQueryThatShouldReturnABuilding()
	.With(b=>b.Manager)
	.With(m=>m.ContactInfo)
	.With(c=>c.PhoneNumber);

Explanation:

The With() extension method will only evaluate the expression it is passed if the value it is executing off of is not null, otherwise it returns null. This means that a null result anywhere in the chain will cause the rest of the With() calls to not evaluate their expression, and instead pass the null safely to the end of the chain.

What With() looks like under the covers

public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
	where TSource : class
{
	if (source != default(TSource))
	{
		return action(source);
	}
	else
	{
	        return default(TResult);
	}
}


What if we don’t care about storing the phone number in a variable. What if instead we just want to dial the number if it’s found.


C#

	Building building = SomeQueryThatShouldReturnABuilding();

	if(building != null)
	{
		if(building.Manager != null)
		{
			if(building.Manager.ContactInfo != null)
			{
				if(building.Manager.ContactInfo.PhoneNumber != null)
				{
					Dial(building.Manager.ContactInfo.PhoneNumber)
				}
			}
		}
	}

C# with monads.net

	SomeQueryThatShouldReturnABuilding()
	.With(b=>b.Manager)
	.With(m=>m.ContactInfo)
	.With(c=>c.PhoneNumber)
	.Do((p)=>{ Dial(p);});

Explanation:

The Do method is similar to the With Method. If the current value of the chain is null, the expression will not be evaluated and a null will be returned instead of the result of the expression.

What Do() looks like under the covers

public static TSource Do<TSource>(this TSource source, Action<TSource> action)
	where TSource : class
{
	if (source != default(TSource))
	{
		action(source);
	}
	return source;
}

And what if we only want to dial the phone number if the manager is at work?

C#

	var Building = SomeQueryThatShouldReturnABuilding();

	if(building != null)
	{
		if(building.Manager != null && building.Manager.isAtWork)
		{
			if(building.Manager.ContactInfo != null)
			{
				if(building.Manager.ContactInfo.PhoneNumber != null)
				{
					Dial(building.Manager.ContactInfo.PhoneNumber)
				}
			}
		}
	}

C# with monads.net

	SomeQueryThatShouldReturnABuilding()
	.With(b=>b.Manager)
	.If(m=>m.isAtWork)
	    .With(m=>m.ContactInfo)
	    .With(c=>c.PhoneNumber)
	    .Do((p)=>{ Dial(p);});

Explanation:

The If method will return a null if the current value is null or the boolean expression it is passed evaluates to false. This will cause the rest of the chain to be aborted.

What If() looks like under the covers

public static TSource If<TSource>(this TSource source, Func<TSource, bool> condition)
	where TSource : class
{
	if ((source != default(TSource)) && (condition(source) == true))
	{
		return source;
	}
	else
	{
		return default(TSource);
	}
}

Cool. If we find a manager and the manager is at work and we have the managers telephone number, we’re dialing it. But what if we want to log to a server or file along the way? An elegant way to handle this is to create our own logging “Monad,” which is another chainable extension method that can be used anywhere in our logic chain.

Our Log() looks like this. It will log the current value and a message if the current value isn’t null. This method will always return the value passed into it even if that value is null. This allows the chaining to continue similar to With(), Do(), and If().

	public static T Log(this T currentValue, string message) 
        where T : class
    {
        if (currentValue != default(T))
            Console.WriteLine(currentValue + message);

        return currentValue;
    }

C#

	Building building = SomeQueryThatShouldReturnABuilding();

	if(building != null)
	{
		if(building.Manager != null)
		{
			Console.WriteLine("Found Manager");
			if(building.Manager.isAtWork)
			{
				Console.WriteLine("Manager is at work");
				if(building.Manager.ContactInfo != null)
				{
					if(building.Manager.ContactInfo.PhoneNumber != null)
					{
						Console.WriteLine("Dialing The Manager");
						Dial(building.Manager.ContactInfo.PhoneNumber)
					}
				}
			}
		}
	}

C# with monads.net

	  SomeQueryThatShouldReturnABuilding()
	 .With(b=>b.Manager)
	 .Log("Found Manager")
	 .If(m=>m.isAtWork)
	     .Log("Manager is at work")
	     .With(m=>m.ContactInfo)
	     .With(c=>c.PhoneNumber)
	     .Log("Dialing The Manager")
	     .Do((p)=>{ Dial(p);});

Explanation:

Look at that, we made a logging monad that behaves the same way as .With(), .Do(), and .If().

Conclusion

Your day to day work life may never involve functional languages like Haskell or F#, but the mind opening concepts they tout can lead to wonderful coding patterns – And that knowledge is useful in any language.

If you want to learn more you should check out this excellent post over at adit.io

642 comments , permalink


Tagged , , ,