Skip to content

Instantly share code, notes, and snippets.

@SlyNet
Last active August 29, 2015 14:00
Show Gist options
  • Save SlyNet/11223318 to your computer and use it in GitHub Desktop.
Save SlyNet/11223318 to your computer and use it in GitHub Desktop.
public static class Monads
{
/// <summary>
/// Returns the value of an expression, or <c>default(T)</c> if any parts of the expression are <c>null</c>.
/// </summary>
/// <typeparam name="T">The type of the Expression</typeparam>
/// <param name="expression">A parameterless lambda representing the path to the value.</param>
/// <returns>The value of the expression, or <c>default(T)</c> if any parts of the expression are <c>null</c>.</returns>
public static T Maybe<T>(Expression<Func<T>> expression)
{
var value = Maybe(expression.Body);
if (value == null) return default(T);
return (T)value;
}
private static object Maybe(Expression expression)
{
var constantExpression = expression as ConstantExpression;
if (constantExpression != null)
{
return constantExpression.Value;
}
var memberExpression = expression as MemberExpression;
if (memberExpression != null)
{
var memberValue = Maybe(memberExpression.Expression);
if (memberValue != null)
{
var member = memberExpression.Member;
return GetValue(member, memberValue);
}
}
var methodCallExpression = expression as MethodCallExpression;
if (methodCallExpression != null)
{
var methodValue = Maybe(methodCallExpression.Object);
var arguments = methodCallExpression.Arguments.Select(Maybe).ToArray();
return GetValue(methodCallExpression.Method, methodValue, arguments);
}
var unaryExpression = expression as UnaryExpression;
if (unaryExpression != null && unaryExpression.NodeType == ExpressionType.Convert)
{
var result = Maybe(unaryExpression.Operand);
if (result != null && IsNullableType(unaryExpression.Type))
{
return Activator.CreateInstance(unaryExpression.Type, result);
}
return result;
}
var binaryExpression = expression as BinaryExpression;
if (binaryExpression != null && binaryExpression.NodeType == ExpressionType.Coalesce)
{
return Maybe(binaryExpression.Left) ?? Maybe(binaryExpression.Right);
}
return null;
}
private static object GetValue(MemberInfo member, object memberValue, object[] arguments = null)
{
var propertyInfo = member as PropertyInfo;
if (propertyInfo != null)
{
return propertyInfo.GetValue(memberValue, null);
}
var fieldInfo = member as FieldInfo;
if (fieldInfo != null)
{
return fieldInfo.GetValue(memberValue);
}
var methodInfo = member as MethodInfo;
if (methodInfo != null && (memberValue != null || methodInfo.IsStatic))
{
return methodInfo.Invoke(memberValue, arguments);
}
return null;
}
private static bool IsNullableType(Type theType)
{
return (theType.IsGenericType && theType.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
[TestFixture]
public class MonadsTests
{
[Test]
public void should_work_correctly_with_methods()
{
var testVariable = new ClassWithMethods();
long x = testVariable.Get(3).X;
Assert.That(x, Is.EqualTo(3));
x = Monads.Maybe(() => testVariable.Get(3).X);
Assert.That(x, Is.EqualTo(3));
x = Monads.Maybe(() => testVariable.GetDefault().X);
Assert.That(x, Is.EqualTo(42));
x = Monads.Maybe(() => testVariable.GetItself().Get(100500).X);
Assert.That(x, Is.EqualTo(100500));
x = Monads.Maybe(() => testVariable.GetItself().GetNull().X);
Assert.That(x, Is.EqualTo(o));
}
[Test]
public void should_work_correctly_with_nullables()
{
var testVariable = new ClassWithNestedObject();
long x = Monads.Maybe(() => testVariable.Field.X);
Assert.That(x, Is.EqualTo(o));
var nullableX = Monads.Maybe<long?>(() => testVariable.Field.X);
Assert.That(nullableX, Is.Null);
testVariable.Field = new NestedClass
{
X = 3
};
x = Monads.Maybe(() => testVariable.Field.X);
Assert.That(x, Is.EqualTo(3));
nullableX = Monads.Maybe<long?>(() => testVariable.Field.X);
Assert.That(nullableX, Is.EqualTo(3));
}
[Test]
public void should_work_correctly_with_null_coalesce_operator()
{
int? nullValue = null;
int? notNullValue = 3;
var nullResult = Monads.Maybe(() => nullValue ?? nullValue);
Assert.That(nullResult, Is.EqualTo(nullValue));
var notNullResultFirst = Monads.Maybe(() => notNullValue ?? nullValue);
Assert.That(notNullResultFirst, Is.EqualTo(notNullValue));
var notNullResultSecond = Monads.Maybe(() => nullValue ?? notNullValue);
Assert.That(notNullResultSecond, Is.EqualTo(notNullValue));
int? anotherNotNullValue = 5;
var anotherNotNullResult = Monads.Maybe(() => anotherNotNullValue ?? notNullValue);
Assert.That(anotherNotNullResult, Is.EqualTo(anotherNotNullValue));
}
[Test]
public void Should_return_null_for_nullable_types_with_null_value_When_called_method_on_them()
{
var testClass = new NestedClass();
string result = Monads.Maybe(() => testClass.Y.ToString());
Assert.That(result, Is.Null);
}
[Test]
public void Should_support_static_methods_wrap()
{
var result= Monads.Maybe(() => string.Format("{0}", "42"));
Assert.That(result, Is.EqualTo("42"), "Expression inside monad with static method should be called and return result");
}
[Test]
public void Should_support_extension_methods()
{
// arrange
CaseWorker caseWorker = Create.ProcessModel.CaseWorker().WithAgent(Create.UserAgent().Build()).Build();
// act
IUserAware userAware = Monads.Maybe(() => caseWorker.As<IUserAware>());
// assert
Assert.That(userAware, Is.Not.Null, "'As' extension call should return value");
}
[Test]
public void Should_support_cast_expressions()
{
var result = Monads.Maybe(() => string.Format("{0}", 42));
Assert.That(result, Is.EqualTo("42"), "Expression inside monad with static method should be called and return result");
}
private class NestedClass
{
public long X { get; set; }
public long? Y { get; set; }
}
private class ClassWithNestedObject
{
public NestedClass Field { get; set; }
}
private class ClassWithMethods
{
public NestedClass Get(long x)
{
return new NestedClass
{
X = x
};
}
public NestedClass GetDefault()
{
return new NestedClass
{
X = 42
};
}
public ClassWithMethods GetItself()
{
return this;
}
public NestedClass GetNull()
{
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment