diff --git a/Funcky/Monads/Option.cs b/Funcky/Monads/Option.cs deleted file mode 100644 index cdd47cc6..00000000 --- a/Funcky/Monads/Option.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Funcky.GenericConstraints; - -namespace Funcky.Monads -{ - public readonly struct Option : IToString - where TItem : notnull - { - private readonly bool _hasItem; - private readonly TItem _item; - - internal Option(TItem item) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - _item = item; - _hasItem = true; - } - - public static bool operator ==(Option lhs, Option rhs) => lhs.Equals(rhs); - - public static bool operator !=(Option lhs, Option rhs) => !lhs.Equals(rhs); - - public static Option None() => default; - - public Option Select(Func selector) - where TResult : notnull - => _hasItem - ? Option.Some(selector(_item)) - : Option.None(); - - public Option SelectMany(Func> maybeSelector, Func resultSelector) - where TResult : notnull - where TMaybe : notnull - { - if (_hasItem) - { - var selectedMaybe = maybeSelector(_item); - if (selectedMaybe._hasItem) - { - return Option.Some(resultSelector(_item, selectedMaybe._item)); - } - } - - return Option.None(); - } - - public Option Where(Func predicate) - => AndThen(item => predicate(item) ? Option.Some(item) : None()); - - public TResult Match(TResult none, Func some) - => _hasItem - ? some(_item) - : none; - - public TResult Match(Func none, Func some) - => _hasItem - ? some(_item) - : none(); - - public void Match(Action none, Action some) - { - if (_hasItem) - { - some(_item); - } - else - { - none(); - } - } - - public Option OrElse(Option elseOption) - => _hasItem - ? this - : elseOption; - - public TItem OrElse(TItem elseOption) - => _hasItem - ? _item - : elseOption; - - public Option OrElse(Func> elseOption) - => _hasItem - ? this - : elseOption.Invoke(); - - public TItem OrElse(Func elseOption) - => _hasItem - ? _item - : elseOption.Invoke(); - - public Option AndThen(Func andThenFunction) - where TResult : notnull - => _hasItem - ? Option.Some(andThenFunction(_item)) - : Option.None(); - - public Option AndThen(Func> andThenFunction) - where TResult : notnull - => _hasItem - ? andThenFunction(_item) - : Option.None(); - - public void AndThen(Action andThenFunction) - { - if (_hasItem) - { - andThenFunction(_item); - } - } - - /// - /// Returns an that yields exactly one value when the option - /// has an item and nothing when the option is empty. - /// - public IEnumerable ToEnumerable() - => Match( - none: Enumerable.Empty(), - some: value => Enumerable.Repeat(value, 1)); - - public override bool Equals(object obj) - => obj is Option other - && Equals(_item, other._item); - - public override int GetHashCode() => - Select(item => item.GetHashCode()).OrElse(0); - - public override string ToString() - => Match( - none: "None", - some: value => $"Some({value})"); - } - - public static class Option - { - public static Option Some(TItem item) - where TItem : notnull - => new Option(item); - - public static Option Some(Option item) - where TItem : notnull - => item; - - /// - /// Creates an from a nullable value. - /// - public static Option From(T? item, RequireClass? ω = null) - where T : class - => item is { } value ? Some(value) : Option.None(); - - /// - public static Option From(T item, RequireStruct? ω = null) - where T : struct - => Some(item); - - /// - public static Option From(T? item) - where T : struct - => item.HasValue ? Some(item.Value) : Option.None(); - } -} diff --git a/Funcky/Monads/Option/Option.Convenience.cs b/Funcky/Monads/Option/Option.Convenience.cs new file mode 100644 index 00000000..395a619b --- /dev/null +++ b/Funcky/Monads/Option/Option.Convenience.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static Funcky.Functional; + +namespace Funcky.Monads +{ + public readonly partial struct Option + { + public Option Where(Func predicate) + => SelectMany(item => predicate(item) ? Option.Some(item) : None()); + + public Option OrElse(Option elseOption) + => Match(none: elseOption, some: Option.Some); + + public TItem OrElse(TItem elseOption) + => Match(none: elseOption, some: Identity); + + public Option OrElse(Func> elseOption) + => Match(none: elseOption, some: Option.Some); + + public TItem OrElse(Func elseOption) + => Match(none: elseOption, some: Identity); + + public Option AndThen(Func andThenFunction) + where TResult : notnull + => Select(andThenFunction); + + public Option AndThen(Func> andThenFunction) + where TResult : notnull + => SelectMany(andThenFunction); + + public void AndThen(Action andThenFunction) + => Match(none: NoOperation, some: andThenFunction); + + /// + /// Returns an that yields exactly one value when the option + /// has an item and nothing when the option is empty. + /// + public IEnumerable ToEnumerable() + => Match( + none: Enumerable.Empty(), + some: value => Enumerable.Repeat(value, 1)); + } +} diff --git a/Funcky/Monads/Option/Option.Core.cs b/Funcky/Monads/Option/Option.Core.cs new file mode 100644 index 00000000..2cce92c3 --- /dev/null +++ b/Funcky/Monads/Option/Option.Core.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Funcky.GenericConstraints; + +namespace Funcky.Monads +{ + public readonly partial struct Option : IToString + where TItem : notnull + { + private readonly bool _hasItem; + private readonly TItem _item; + + internal Option(TItem item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + _item = item; + _hasItem = true; + } + + public static bool operator ==(Option lhs, Option rhs) => lhs.Equals(rhs); + + public static bool operator !=(Option lhs, Option rhs) => !lhs.Equals(rhs); + + public static Option None() => default; + + public Option Select(Func selector) + where TResult : notnull + => Match( + none: Option.None, + item => Option.Some(selector(item))); + + public Option SelectMany(Func> selector) + where TResult : notnull + => SelectMany(selector, (_, result) => result); + + public Option SelectMany(Func> maybeSelector, Func resultSelector) + where TResult : notnull + where TMaybe : notnull + => Match( + none: Option.None, + some: item => maybeSelector(item).Select( + maybe => resultSelector(item, maybe))); + + public TResult Match(TResult none, Func some) + => Match(() => none, some); + + public TResult Match(Func none, Func some) + => _hasItem + ? some(_item) + : none(); + + public void Match(Action none, Action some) + { + if (_hasItem) + { + some(_item); + } + else + { + none(); + } + } + + public override bool Equals(object obj) + => obj is Option other && Equals(_item, other._item); + + public override int GetHashCode() + => Match( + none: 0, + some: item => item.GetHashCode()); + + public override string ToString() + => Match( + none: "None", + some: value => $"Some({value})"); + } + + public static partial class Option + { + public static Option Some(TItem item) + where TItem : notnull + => new Option(item); + + public static Option Some(Option item) + where TItem : notnull + => item; + } +} diff --git a/Funcky/Monads/Option/Option.FromNullable.cs b/Funcky/Monads/Option/Option.FromNullable.cs new file mode 100644 index 00000000..c63004b3 --- /dev/null +++ b/Funcky/Monads/Option/Option.FromNullable.cs @@ -0,0 +1,24 @@ +using Funcky.GenericConstraints; + +namespace Funcky.Monads +{ + public static partial class Option + { + /// + /// Creates an from a nullable value. + /// + public static Option From(T? item, RequireClass? ω = null) + where T : class + => item is { } value ? Some(value) : Option.None(); + + /// + public static Option From(T item, RequireStruct? ω = null) + where T : struct + => Some(item); + + /// + public static Option From(T? item) + where T : struct + => item.HasValue ? Some(item.Value) : Option.None(); + } +} diff --git a/changelog.md b/changelog.md index 6978a856..7d1aaeac 100644 --- a/changelog.md +++ b/changelog.md @@ -16,3 +16,5 @@ ## Unreleased * Added overload for AndThen which flattens the Option * Add `Where` method to `Option`, which allows filtering the `Option` by a predicate. +* Add overload for `Option.SelectMany` that takes only a selector. + \ No newline at end of file