using System;
namespace Sprache
{
///
/// Represents an optional result.
///
/// The result type.
public interface IOption
{
///
/// Gets a value indicating whether this instance is empty.
///
bool IsEmpty { get; }
///
/// Gets a value indicating whether this instance is defined.
///
bool IsDefined { get; }
///
/// Gets the matched result or a default value.
///
///
T GetOrDefault();
///
/// Gets the matched result.
///
T Get();
}
///
/// Extensions for .
///
public static class OptionExtensions
{
///
/// Gets the value or else returns a default value.
///
/// The result type.
///
/// The default value.
///
public static T GetOrElse(this IOption option, T defaultValue)
{
if (option == null) throw new ArgumentNullException(nameof(option));
return option.IsEmpty ? defaultValue : option.Get();
}
///
/// Maps a function over the value or else returns an empty option.
///
/// The input type.
/// The output type.
/// The option containing the value to apply to.
/// The function to apply to the value of .
/// An options result containing the result if there was an input value.
public static IOption Select(this IOption option, Func map)
{
if (option == null) throw new ArgumentNullException(nameof(option));
return option.IsDefined ? (IOption) new Some(map(option.Get())) : new None();
}
///
/// Binds the value to a function with optional result and flattens the result to a single optional.
/// A result projection is applied aftherwards.
///
/// The input type.
/// The output type of .
/// The final output type.
/// The option containing the value to bind to.
/// The function that receives the input values and returns an optional value.
/// The function that is projects the result of .
/// An option result containing the result if there were was an input value and bind result.
public static IOption SelectMany(this IOption option, Func> bind, Func project)
{
if (option == null) throw new ArgumentNullException(nameof(option));
if (option.IsEmpty) return new None();
var t = option.Get();
return bind(t).Select(u => project(t,u));
}
///
/// Binds the value to a function with optional result and flattens the result to a single optional.
///
/// The input type.
/// The output type.
/// The option containing the value to bind to.
/// The function that receives the input values and returns an optional value.
/// An option result containing the result if there were was an input value and bind result.
public static IOption SelectMany(this IOption option, Func> bind) => option.SelectMany(bind, (_,x) => x);
}
internal abstract class AbstractOption : IOption
{
public abstract bool IsEmpty { get; }
public bool IsDefined
{
get { return !IsEmpty; }
}
public T GetOrDefault()
{
return IsEmpty ? default(T) : Get();
}
public abstract T Get();
}
internal sealed class Some : AbstractOption
{
private readonly T _value;
public Some(T value)
{
_value = value;
}
public override bool IsEmpty
{
get { return false; }
}
public override T Get()
{
return _value;
}
}
internal sealed class None : AbstractOption
{
public override bool IsEmpty
{
get { return true; }
}
public override T Get()
{
throw new InvalidOperationException("Cannot get value from None.");
}
}
}