Skip to content

Instantly share code, notes, and snippets.

@tomaszpolanski
Last active August 29, 2015 14:23
Show Gist options
  • Save tomaszpolanski/9e96bc553d480af0eb6d to your computer and use it in GitHub Desktop.
Save tomaszpolanski/9e96bc553d480af0eb6d to your computer and use it in GitHub Desktop.
Implementation of optional for C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace Common
{
public abstract class Option<T>
{
public static readonly None<T> None = new None<T>();
public static Option<T> OfObject(T value)
{
return value != null ? (Option<T>)new Some<T>(value) : new None<T>();
}
public static Option<T> Try(Func<T> func)
{
try
{
return OfObject(func.Invoke());
}
catch (Exception)
{
return new None<T>();
}
}
public abstract Option<OUT> Select<OUT>(Func<T, OUT> selector);
public abstract Option<OUT> SelectMany<OUT>(Func<T, Option<OUT>> selector);
public abstract Option<T> Where(Func<T, bool> predicate);
public abstract IEnumerable<T> ToEnumerable();
public abstract OUT Match<OUT>(Func<T, OUT> some, Func<OUT> none);
public abstract void Iter(Action<T> action);
public abstract T OrDefault(Func<T> def);
public abstract T GetUnsafe { get; }
public abstract bool IsSome { get; }
}
public sealed class Some<T> : Option<T>
{
private readonly T _value;
public override T GetUnsafe
{
get { return _value; }
}
public override bool IsSome
{
get { return true; }
}
internal Some(T value)
{
_value = value;
}
public override Option<OUT> Select<OUT>(Func<T, OUT> selector)
{
return new Some<OUT>(selector.Invoke(_value));
}
public override Option<OUT> SelectMany<OUT>(Func<T, Option<OUT>> selector)
{
return selector.Invoke(_value);
}
public override Option<T> Where(Func<T, bool> predicate)
{
return predicate.Invoke(_value) ? (Option<T>) this : new None<T>();
}
public override IEnumerable<T> ToEnumerable()
{
yield return _value;
}
public override OUT Match<OUT>(Func<T, OUT> some, Func<OUT> none)
{
return some.Invoke(_value);
}
public override void Iter(Action<T> action)
{
action.Invoke(_value);
}
public override T OrDefault(Func<T> def)
{
return _value;
}
}
public sealed class None<T> : Option<T>
{
internal None()
{ }
public override T GetUnsafe
{
get { throw new InvalidOperationException("The option is None"); }
}
public override bool IsSome
{
get { return false; }
}
public override Option<OUT> Select<OUT>(Func<T, OUT> selector)
{
return new None<OUT>();
}
public override Option<OUT> SelectMany<OUT>(Func<T, Option<OUT>> selector)
{
return new None<OUT>();
}
public override Option<T> Where(Func<T, bool> predicate)
{
return this;
}
public override IEnumerable<T> ToEnumerable()
{
return Enumerable.Empty<T>();
}
public override OUT Match<OUT>(Func<T, OUT> some, Func<OUT> none)
{
return none.Invoke();
}
public override void Iter(Action<T> action)
{
// Do nothing
}
public override T OrDefault(Func<T> def)
{
return def.Invoke();
}
}
}
namespace Common.Tests
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
[TestClass]
public class TestOption
{
private static void AssertSome<T>(Option<T> option)
{
Assert.IsTrue(option.Match(_ => true, () => false), "Option is None");
}
private static void AssertSome<T>(Option<T> option, T expected)
{
option.Match(
val => Unit.AsUnit(() => Assert.AreEqual(expected, val)),
() => Unit.AsUnit(() => Assert.Fail("Option is None")));
}
private static void AssertNone<T>(Option<T> option)
{
Assert.IsTrue(option.Match(_ => false, () => true), "Option is Some");
}
[TestMethod]
public void TestSuccessfulOfObject()
{
var op = Option<Unit>.OfObject(Unit.Default);
Assert.IsInstanceOfType(op, typeof(Some<Unit>));
}
[TestMethod]
public void TestFailedOfObject()
{
var op = Option<Unit>.OfObject(null);
Assert.IsInstanceOfType(op, typeof(None<Unit>));
}
[TestMethod]
public void TestSuccessfulMatch()
{
var op = Option<Unit>.OfObject(Unit.Default);
AssertSome(op);
}
[TestMethod]
public void TestFailedMatch()
{
var op = Option<Unit>.OfObject(null);
AssertNone(op);
}
[TestMethod]
public void TestSuccessfulSelect()
{
const string str = "Something";
var op = Option<Unit>.OfObject(Unit.Default)
.Select(_ => str);
var test = Enumerable.Range(0, 1000)
.Choose(Create);
AssertSome(op, str);
}
public static Option<string> Create(int id)
{
return Option<int>.OfObject(id)
.Where(i => i % 2 == 0)
.Select(i => "" + i);
}
[TestMethod]
public void TestFailedSelect()
{
const string str = "Something";
var op = Option<Unit>.OfObject(null)
.Select(_ => str);
AssertNone(op);
}
[TestMethod]
public void TestSuccessfulSelectMany()
{
const string str = "Something";
var op = Option<Unit>.OfObject(Unit.Default)
.SelectMany(_ => Option<string>.OfObject(str));
AssertSome(op, str);
}
[TestMethod]
public void TestFailedSelectMany()
{
const string str = "Something";
var op = Option<Unit>.OfObject(null)
.SelectMany(_ => Option<string>.OfObject(str));
AssertNone(op);
}
[TestMethod]
public void TestSuccessfulWhere()
{
const string str = "Something";
var op = Option<string>.OfObject(str)
.Where(s => s.Equals(str));
AssertSome(op, str);
}
[TestMethod]
public void TestFailedWhereWhenFiltering()
{
const string str = "Something";
var op = Option<string>.OfObject(str)
.Where(s => s.Equals(""));
AssertNone(op);
}
[TestMethod]
public void TestFailedWhere()
{
const string str = "Something";
var op = Option<Unit>.OfObject(null)
.Where(s => s.Equals(str));
AssertNone(op);
}
[TestMethod]
public void TestSuccessfulTry()
{
var op = Option<Unit>.Try(() => Unit.Default);
AssertSome(op, Unit.Default);
}
[TestMethod]
public void TestFailedTry()
{
var op = Option<Unit>.Try(() => { throw new System.Exception(); });
AssertNone(op);
}
[TestMethod]
public void TestSuccessfulIter()
{
bool changed = false;
Option<Unit>.OfObject( Unit.Default)
.Iter(_ => changed = true);
Assert.IsTrue(changed);
}
[TestMethod]
public void TestFailedIter()
{
bool changed = false;
Option<Unit>.None
.Iter(_ => changed = true);
Assert.IsFalse(changed);
}
[TestMethod]
public void TestSuccessfulOrDefault()
{
bool val = false;
var result = Option<bool>.OfObject(val)
.OrDefault(() => true);
Assert.AreEqual(val, result);
}
[TestMethod]
public void TestFailedOrDefault()
{
var result = Option<bool>.None
.OrDefault(() => true);
Assert.IsTrue(result);
}
}
}
using System;
namespace Common
{
public sealed class Unit
{
public static readonly Unit Default = new Unit();
public static Unit AsUnit(Action action)
{
action.Invoke();
return Default;
}
private Unit() { }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment