Last active
August 29, 2015 14:23
-
-
Save tomaszpolanski/9e96bc553d480af0eb6d to your computer and use it in GitHub Desktop.
Implementation of optional for C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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