From cbed6e0c312c220c008beca1fe25c5e6a16cf15b Mon Sep 17 00:00:00 2001 From: Tim Alberdingk Thijm Date: Fri, 8 Dec 2023 10:47:58 -0500 Subject: [PATCH 1/2] Add additional `Option` methods Add `Option` equivalents of the following `System.Linq.Enumerable` methods: * `Intersect`: a kind of eager Boolean "and" over 2 options. Returns the second option unless the first is None. * `Union`: a kind of eager Boolean "or" over 2 options. Returns the first option unless it is None. * `SelectMany`: a chainable version of Select akin to `Enumerable.SelectMany`. The supplied function returns an option rather than a bare type, and the result type is the function's result type. * `SomeOrDefault`: a kind of `Enumerable.Concat` over an option and an option-generating thunk. If the option is None, call the thunk to generate a default option. All names of these new methods are open to revision. These are less common methods and their names vary in other programming languages. Alternatives might be: * Option.Intersect --> Option.And * Option.Union --> Option.Or * Option.SelectMany --> Option.Bind or Option.AndThen * Option.SomeOrDefault --> Option.Concat or Option.OrElse SomeOrDefault is called `or_else` in Rust and C++23. SelectMany is variously called `and_then` (Rust, C++23, Elm), `bind` (OCaml), or ``flat_map` (Nim, Swift). --- ZenLib.Tests/OptionTests.cs | 605 ++++++++++++++++++++---------------- ZenLib/DataTypes/Option.cs | 120 ++++++- 2 files changed, 462 insertions(+), 263 deletions(-) diff --git a/ZenLib.Tests/OptionTests.cs b/ZenLib.Tests/OptionTests.cs index cda2744..09eaed3 100644 --- a/ZenLib.Tests/OptionTests.cs +++ b/ZenLib.Tests/OptionTests.cs @@ -4,285 +4,370 @@ namespace ZenLib.Tests { - using System.Diagnostics.CodeAnalysis; - using System.Numerics; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using ZenLib; - using ZenLib.Tests.Network; - using static ZenLib.Tests.TestHelper; - using static ZenLib.Zen; + using System.Diagnostics.CodeAnalysis; + using System.Numerics; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using ZenLib; + using ZenLib.Tests.Network; + using static ZenLib.Tests.TestHelper; + using static ZenLib.Zen; + + /// + /// Tests for the Zen option type. + /// + [TestClass] + [ExcludeFromCodeCoverage] + public class OptionTests + { + /// + /// Test that option equality works. + /// + [TestMethod] + public void TestOptionEquality() + { + Assert.AreNotEqual(Option.None(), null); + Assert.AreNotEqual(Option.None(), 4); + Assert.AreEqual(Option.None(), Option.None()); + Assert.AreEqual(new Option(false, 1), new Option(false, 2)); + Assert.AreNotEqual(Option.None(), Option.Some(0)); + Assert.AreNotEqual(Option.Some(0), Option.None()); + Assert.AreEqual(Option.Some(0), Option.Some(0)); + Assert.AreNotEqual(Option.Some(0), Option.Some(1)); + Assert.AreNotEqual(Option.None().GetHashCode(), Option.Some(1).GetHashCode()); + } /// - /// Tests for the Zen option type. + /// Test that finding an option works. /// - [TestClass] - [ExcludeFromCodeCoverage] - public class OptionTests + [TestMethod] + public void TestOptionFind() { - /// - /// Test that option equality works. - /// - [TestMethod] - public void TestOptionEquality() - { - Assert.AreNotEqual(Option.None(), null); - Assert.AreNotEqual(Option.None(), 4); - Assert.AreEqual(Option.None(), Option.None()); - Assert.AreEqual(new Option(false, 1), new Option(false, 2)); - Assert.AreNotEqual(Option.None(), Option.Some(0)); - Assert.AreNotEqual(Option.Some(0), Option.None()); - Assert.AreEqual(Option.Some(0), Option.Some(0)); - Assert.AreNotEqual(Option.Some(0), Option.Some(1)); - Assert.AreNotEqual(Option.None().GetHashCode(), Option.Some(1).GetHashCode()); - } - - /// - /// Test that finding an option works. - /// - [TestMethod] - public void TestOptionFind() - { - var zf = new ZenFunction, bool>(o => o.IsSome()); - var example = zf.Find((i, o) => o); - Assert.IsTrue(example.HasValue); - Assert.AreEqual(Option.Some(0), example.Value); - } - - /// - /// Test none has no value. - /// - [TestMethod] - public void TestOptionNone() - { - CheckAgreement>(o => o.IsSome()); - } - - /// - /// Test some returns the correct value. - /// - [TestMethod] - public void TestOptionSomeInt() - { - RandomBytes(x => CheckAgreement>(o => And(o.IsSome(), o.Value() == Constant(x)))); - } - - /// - /// Test option with tuple underneath. - /// - [TestMethod] - public void TestOptionSomeTuple() - { - RandomBytes(x => - { - CheckAgreement>>(o => - { - var item1 = o.Value().Item1() == x; - var item2 = o.Value().Item2() == x; - return And(o.IsSome(), Or(item1, item2)); - }); - }); - } - - /// - /// Test option match. - /// - [TestMethod] - public void TestOptionMatch() - { - CheckValid>(o => - Implies(And(o.IsSome(), o.Value() <= Constant(4)), - o.Case(none: () => False(), some: v => v <= Constant(4)))); - } - - /// - /// Test option value or. - /// - [TestMethod] - public void TestOptionNullValueType() - { - CheckAgreement>>(o => o.Value().Item1() == o.Value().Item2()); - } - - /// - /// Test option value or. - /// - [TestMethod] - public void TestOptionValueOrDefaultValid() - { - CheckValid>(o => o.Select(v => Constant(2)).ValueOrDefault(Constant(2)) == Constant(2)); - } - - /// - /// Test option value or default. - /// - [TestMethod] - public void TestOptionValueOrDefault() + var zf = new ZenFunction, bool>(o => o.IsSome()); + var example = zf.Find((i, o) => o); + Assert.IsTrue(example.HasValue); + Assert.AreEqual(Option.Some(0), example.Value); + } + + /// + /// Test none has no value. + /// + [TestMethod] + public void TestOptionNone() + { + CheckAgreement>(o => o.IsSome()); + } + + /// + /// Test some returns the correct value. + /// + [TestMethod] + public void TestOptionSomeInt() + { + RandomBytes(x => CheckAgreement>(o => And(o.IsSome(), o.Value() == Constant(x)))); + } + + /// + /// Test option with tuple underneath. + /// + [TestMethod] + public void TestOptionSomeTuple() + { + RandomBytes(x => + { + CheckAgreement>>(o => { - var zf = new ZenFunction, int, int>((o, i) => o.ValueOrDefault(i)); + var item1 = o.Value().Item1() == x; + var item2 = o.Value().Item2() == x; + return And(o.IsSome(), Or(item1, item2)); + }); + }); + } - Assert.AreEqual(1, zf.Evaluate(Option.Some(1), 3)); - Assert.AreEqual(3, zf.Evaluate(Option.None(), 3)); + /// + /// Test option match. + /// + [TestMethod] + public void TestOptionMatch() + { + CheckValid>(o => + Implies(And(o.IsSome(), o.Value() <= Constant(4)), + o.Case(none: () => False(), some: v => v <= Constant(4)))); + } - zf.Compile(); - Assert.AreEqual(1, zf.Evaluate(Option.Some(1), 3)); - Assert.AreEqual(3, zf.Evaluate(Option.None(), 3)); + /// + /// Test option value or. + /// + [TestMethod] + public void TestOptionNullValueType() + { + CheckAgreement>>(o => o.Value().Item1() == o.Value().Item2()); + } - Assert.AreEqual(1, Option.Some(1).ValueOrDefault(3)); - Assert.AreEqual(3, Option.None().ValueOrDefault(3)); - } + /// + /// Test option value or. + /// + [TestMethod] + public void TestOptionValueOrDefaultValid() + { + CheckValid>(o => o.Select(v => Constant(2)).ValueOrDefault(Constant(2)) == Constant(2)); + } - /// - /// Test option IsSome. - /// - [TestMethod] - public void TestOptionIsSome() - { - var zf = new ZenFunction, bool>(o => o.IsSome()); + /// + /// Test option value or default. + /// + [TestMethod] + public void TestOptionValueOrDefault() + { + var zf = new ZenFunction, int, int>((o, i) => o.ValueOrDefault(i)); - Assert.AreEqual(true, zf.Evaluate(Option.Some(1))); - Assert.AreEqual(false, zf.Evaluate(Option.None())); + Assert.AreEqual(1, zf.Evaluate(Option.Some(1), 3)); + Assert.AreEqual(3, zf.Evaluate(Option.None(), 3)); - zf.Compile(); - Assert.AreEqual(true, zf.Evaluate(Option.Some(1))); - Assert.AreEqual(false, zf.Evaluate(Option.None())); + zf.Compile(); + Assert.AreEqual(1, zf.Evaluate(Option.Some(1), 3)); + Assert.AreEqual(3, zf.Evaluate(Option.None(), 3)); - Assert.AreEqual(true, Option.Some(1).IsSome()); - Assert.AreEqual(false, Option.None().IsSome()); - } + Assert.AreEqual(1, Option.Some(1).ValueOrDefault(3)); + Assert.AreEqual(3, Option.None().ValueOrDefault(3)); + } - /// - /// Test option IsNone. - /// - [TestMethod] - public void TestOptionIsNone() - { - var zf = new ZenFunction, bool>(o => o.IsNone()); + /// + /// Test option some or default. + /// + [TestMethod] + public void TestOptionSomeOrDefault() + { + var zf = new ZenFunction, Option, Option>((o1, o2) => o1.SomeOrDefault(() => o2)); - Assert.AreEqual(false, zf.Evaluate(Option.Some(1))); - Assert.AreEqual(true, zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.Some(3))); + Assert.AreEqual(Option.Some(3), zf.Evaluate(Option.None(), Option.Some(3))); - zf.Compile(); - Assert.AreEqual(false, zf.Evaluate(Option.Some(1))); - Assert.AreEqual(true, zf.Evaluate(Option.None())); + zf.Compile(); + Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.Some(3))); + Assert.AreEqual(Option.Some(3), zf.Evaluate(Option.None(), Option.Some(3))); - Assert.AreEqual(false, Option.Some(1).IsNone()); - Assert.AreEqual(true, Option.None().IsNone()); - } + Assert.AreEqual(Option.Some(1), Option.Some(1).SomeOrDefault(() => Option.Some(3))); + Assert.AreEqual(Option.Some(3), Option.None().SomeOrDefault(() => Option.Some(3))); + } - /// - /// Test option where. - /// - [TestMethod] - public void TestOptionWhereValid() - { - CheckValid>(o => - Implies(And(o.IsSome(), o.Value() <= Constant(4)), Not(o.Where(v => v > Constant(4)).IsSome()))); - } - - /// - /// Test option Where. - /// - [TestMethod] - public void TestOptionWhere() - { - var zf = new ZenFunction, Option>(o => o.Where(i => i > 10)); - - Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1))); - Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); - Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(11))); - - zf.Compile(); - Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1))); - Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); - Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(11))); - - Assert.AreEqual(Option.None(), Option.Some(1).Where(i => i > 10)); - Assert.AreEqual(Option.None(), Option.None().Where(i => i > 10)); - Assert.AreEqual(Option.Some(11), Option.Some(11).Where(i => i > 10)); - } - - /// - /// Test option to sequence. - /// - [TestMethod] - public void TestOptionToSequenceValid1() - { - CheckAgreement>(o => o.ToFSeq().IsEmpty(), runBdds: false); - } - - /// - /// Test option to sequence. - /// - [TestMethod] - public void TestOptionToSequenceValid2() - { - CheckValid>(o => Implies(o.IsSome(), o.ToFSeq().Length() == (BigInteger)1), runBdds: false); - } - - /// - /// Test option ToSequence. - /// - [TestMethod] - public void TestOptionToSequence() - { - var zf = new ZenFunction, FSeq>(o => o.ToFSeq()); + /// + /// Test option IsSome. + /// + [TestMethod] + public void TestOptionIsSome() + { + var zf = new ZenFunction, bool>(o => o.IsSome()); - Assert.AreEqual(1, zf.Evaluate(Option.Some(1)).ToList().Count); - Assert.AreEqual(0, zf.Evaluate(Option.None()).ToList().Count); + Assert.AreEqual(true, zf.Evaluate(Option.Some(1))); + Assert.AreEqual(false, zf.Evaluate(Option.None())); - zf.Compile(); - Assert.AreEqual(1, zf.Evaluate(Option.Some(1)).ToList().Count); - Assert.AreEqual(0, zf.Evaluate(Option.None()).ToList().Count); + zf.Compile(); + Assert.AreEqual(true, zf.Evaluate(Option.Some(1))); + Assert.AreEqual(false, zf.Evaluate(Option.None())); - Assert.AreEqual(1, Option.Some(1).ToSequence().ToList().Count); - Assert.AreEqual(0, Option.None().ToSequence().ToList().Count); - } + Assert.AreEqual(true, Option.Some(1).IsSome()); + Assert.AreEqual(false, Option.None().IsSome()); + } - /// - /// Test option select. - /// - [TestMethod] - public void TestOptionSelectValid() - { - CheckValid>(o => - Implies(o.IsSome(), o.Select(v => v + Constant(1)).Value() == o.Value() + Constant(1))); - } - - /// - /// Test option Where. - /// - [TestMethod] - public void TestOptionSelect() - { - var zf = new ZenFunction, Option>(o => o.Select(i => i + 1)); - - Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); - Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); - Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); - - zf.Compile(); - Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); - Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); - Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); - - Assert.AreEqual(Option.None(), Option.None().Select(i => i + 1)); - Assert.AreEqual(Option.Some(2), Option.Some(1).Select(i => i + 1)); - Assert.AreEqual(Option.Some(11), Option.Some(10).Select(i => i + 1)); - } - - /// - /// Test non-null default. - /// - [TestMethod] - public void TestOptionDefault() - { - var f = new ZenFunction(() => - { - var x = Option.Null(); - return x.Value(); - }); - - Assert.AreEqual(0U, f.Evaluate().DstIp.Value); - } + /// + /// Test option IsNone. + /// + [TestMethod] + public void TestOptionIsNone() + { + var zf = new ZenFunction, bool>(o => o.IsNone()); + + Assert.AreEqual(false, zf.Evaluate(Option.Some(1))); + Assert.AreEqual(true, zf.Evaluate(Option.None())); + + zf.Compile(); + Assert.AreEqual(false, zf.Evaluate(Option.Some(1))); + Assert.AreEqual(true, zf.Evaluate(Option.None())); + + Assert.AreEqual(false, Option.Some(1).IsNone()); + Assert.AreEqual(true, Option.None().IsNone()); + } + + /// + /// Test option where. + /// + [TestMethod] + public void TestOptionWhereValid() + { + CheckValid>(o => + Implies(And(o.IsSome(), o.Value() <= Constant(4)), Not(o.Where(v => v > Constant(4)).IsSome()))); + } + + /// + /// Test option Where. + /// + [TestMethod] + public void TestOptionWhere() + { + var zf = new ZenFunction, Option>(o => o.Where(i => i > 10)); + + Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1))); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(11))); + + zf.Compile(); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1))); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(11))); + + Assert.AreEqual(Option.None(), Option.Some(1).Where(i => i > 10)); + Assert.AreEqual(Option.None(), Option.None().Where(i => i > 10)); + Assert.AreEqual(Option.Some(11), Option.Some(11).Where(i => i > 10)); + } + + /// + /// Test option to sequence. + /// + [TestMethod] + public void TestOptionToSequenceValid1() + { + CheckAgreement>(o => o.ToFSeq().IsEmpty(), runBdds: false); + } + + /// + /// Test option to sequence. + /// + [TestMethod] + public void TestOptionToSequenceValid2() + { + CheckValid>(o => Implies(o.IsSome(), o.ToFSeq().Length() == (BigInteger)1), runBdds: false); + } + + /// + /// Test option ToSequence. + /// + [TestMethod] + public void TestOptionToSequence() + { + var zf = new ZenFunction, FSeq>(o => o.ToFSeq()); + + Assert.AreEqual(1, zf.Evaluate(Option.Some(1)).ToList().Count); + Assert.AreEqual(0, zf.Evaluate(Option.None()).ToList().Count); + + zf.Compile(); + Assert.AreEqual(1, zf.Evaluate(Option.Some(1)).ToList().Count); + Assert.AreEqual(0, zf.Evaluate(Option.None()).ToList().Count); + + Assert.AreEqual(1, Option.Some(1).ToSequence().ToList().Count); + Assert.AreEqual(0, Option.None().ToSequence().ToList().Count); + } + + /// + /// Test option select. + /// + [TestMethod] + public void TestOptionSelectValid() + { + CheckValid>(o => + Implies(o.IsSome(), o.Select(v => v + Constant(1)).Value() == o.Value() + Constant(1))); + } + + /// + /// Test option Select. + /// + [TestMethod] + public void TestOptionSelect() + { + var zf = new ZenFunction, Option>(o => o.Select(i => i + 1)); + + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); + Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); + + zf.Compile(); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); + Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); + + Assert.AreEqual(Option.None(), Option.None().Select(i => i + 1)); + Assert.AreEqual(Option.Some(2), Option.Some(1).Select(i => i + 1)); + Assert.AreEqual(Option.Some(11), Option.Some(10).Select(i => i + 1)); + } + + /// + /// Test option SelectMany. + /// + [TestMethod] + public void TestOptionSelectMany() + { + var zf = new ZenFunction, Option>(o => o.SelectMany(i => Option.Create(i + 1))); + + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); + Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); + + zf.Compile(); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); + Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); + Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); + + Assert.AreEqual(Option.None(), Option.None().SelectMany(i => Option.Some(i + 1))); + Assert.AreEqual(Option.Some(2), Option.Some(1).SelectMany(i => Option.Some(i + 1))); + Assert.AreEqual(Option.Some(11), Option.Some(10).SelectMany(i => Option.Some(i + 1))); + } + + /// + /// Test non-null default. + /// + [TestMethod] + public void TestOptionDefault() + { + var f = new ZenFunction(() => + { + var x = Option.Null(); + return x.Value(); + }); + + Assert.AreEqual(0U, f.Evaluate().DstIp.Value); + } + + /// + /// Test option intersect. + /// + [TestMethod] + public void TestOptionIntersect() + { + var zf = new ZenFunction, Option, Option>((o1, o2) => o1.Intersect(o2)); + + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None(), Option.Some(1))); + Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1), Option.Some(2))); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1), Option.None())); + + zf.Compile(); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None(), Option.Some(1))); + Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1), Option.Some(2))); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1), Option.None())); + + Assert.AreEqual(Option.None(), Option.None().Intersect(Option.Some(1))); + Assert.AreEqual(Option.Some(2), Option.Some(1).Intersect(Option.Some(2))); + Assert.AreEqual(Option.None(), Option.Some(1).Intersect(Option.None())); + } + + /// + /// Test option union. + /// + [TestMethod] + public void TestOptionUnion() + { + var zf = new ZenFunction, Option, Option>((o1, o2) => o1.Union(o2)); + + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None(), Option.None())); + Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.None(), Option.Some(1))); + Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.None())); + + zf.Compile(); + Assert.AreEqual(Option.None(), zf.Evaluate(Option.None(), Option.None())); + Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.None(), Option.Some(1))); + Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.None())); + + Assert.AreEqual(Option.None(), Option.None().Union(Option.None())); + Assert.AreEqual(Option.Some(1), Option.None().Union(Option.Some(1))); + Assert.AreEqual(Option.Some(1), Option.Some(1).Union(Option.None())); } -} + } +} \ No newline at end of file diff --git a/ZenLib/DataTypes/Option.cs b/ZenLib/DataTypes/Option.cs index 0c63435..e48d9e5 100644 --- a/ZenLib/DataTypes/Option.cs +++ b/ZenLib/DataTypes/Option.cs @@ -47,6 +47,22 @@ public T ValueOrDefault(T other) return other; } + /// + /// Return the option if it has a value, or the result of a function if not. + /// Lazy equivalent (without unwrapping) of . + /// + /// The default-generating function. + /// An option of the underlying type. + public Option SomeOrDefault(Func> other) + { + if (this.HasValue) + { + return this; + } + + return other(); + } + /// /// Map a function over an option. /// @@ -58,6 +74,17 @@ public Option Select(Func function) return this.HasValue ? Option.Some(function(this.Value)) : Option.None(); } + /// + /// Map a function that returns an option over an option and "flatten" the result. + /// + /// The function. + /// A new option with the function mapped over the value. + public Option SelectMany(Func> function) + { + Contract.AssertNotNull(function); + return this.HasValue ? function(this.Value) : Option.None(); + } + /// /// Filter an option with a predicate. /// @@ -102,6 +129,30 @@ public FSeq ToSequence() return this.HasValue ? new FSeq().AddFront(this.Value) : new FSeq(); } + /// + /// Return the "intersection" of the option with another: + /// an option with no value if this option has no value, + /// otherwise the other option. + /// + /// The other option. + /// An option. + public Option Intersect(Option other) + { + return !this.HasValue ? this : other; + } + + /// + /// Return the "union" of the option with another: + /// this option if it has a value, + /// otherwise the other option. + /// + /// The other option. + /// An option. + public Option Union(Option other) + { + return this.HasValue ? this : other; + } + /// /// Convert the option to a string. /// @@ -220,6 +271,23 @@ public static Zen> Select(this Zen> expr, Func()); } + /// + /// The Zen expression for mapping (and projecting) over an option. + /// + /// The expression. + /// The function. + /// The expression type. + /// The function return type. + /// Zen value. + public static Zen> SelectMany(this Zen> expr, + Func, Zen>> function) + { + Contract.AssertNotNull(expr); + Contract.AssertNotNull(function); + + return If(expr.IsSome(), function(expr.Value()), Option.Null()); + } + /// /// The Zen expression for filtering over an option. /// @@ -231,7 +299,7 @@ public static Zen> Where(this Zen> expr, Func, Zen Contract.AssertNotNull(expr); Contract.AssertNotNull(function); - return If(And(expr.IsSome(), function(expr.Value())), expr, Option.Null()); + return If(Zen.And(expr.IsSome(), function(expr.Value())), expr, Option.Null()); } /// @@ -248,6 +316,20 @@ public static Zen ValueOrDefault(this Zen> expr, Zen deflt) return If(expr.IsSome(), expr.Value(), deflt); } + /// + /// The Zen expression for an option if it has a value, or the result of a function otherwise. + /// + /// The expression. + /// The function. + /// Zen value. + public static Zen> SomeOrDefault(this Zen> expr, Func>> function) + { + Contract.AssertNotNull(expr); + Contract.AssertNotNull(function); + + return If(expr.IsSome(), expr, function()); + } + /// /// The Zen expression for whether an option has a value. /// @@ -261,7 +343,7 @@ public static Zen IsSome(this Zen> expr) } /// - /// The Zen expression for whether an option has a value. + /// The Zen expression for whether an option has no value. /// /// The expression. /// Zen value. @@ -273,7 +355,7 @@ public static Zen IsNone(this Zen> expr) } /// - /// The Zen expression for whether an option has a value. + /// The Zen expression for representing the option as a finite sequence. /// /// The expression. /// Zen value. @@ -284,5 +366,37 @@ public static Zen> ToFSeq(this Zen> expr) var l = FSeq.Empty(); return If(expr.IsSome(), l.AddFront(expr.Value()), l); } + + /// + /// The Zen expression for an "intersection" of two options. + /// None if the first option is None, otherwise the second value. + /// + /// + /// + /// + /// + public static Zen> Intersect(this Zen> expr1, Zen> expr2) + { + Contract.AssertNotNull(expr1); + Contract.AssertNotNull(expr2); + + return If(expr1.IsNone(), expr1, expr2); + } + + /// + /// The Zen expression for a "union" of two options. + /// The first option if it is Some, otherwise the second value. + /// + /// + /// + /// + /// + public static Zen> Union(this Zen> expr1, Zen> expr2) + { + Contract.AssertNotNull(expr1); + Contract.AssertNotNull(expr2); + + return If(expr1.IsSome(), expr1, expr2); + } } } From c07bbf95ac943e341fd4f3695128b074631cea44 Mon Sep 17 00:00:00 2001 From: Tim Alberdingk Thijm Date: Mon, 8 Jan 2024 13:23:14 -0500 Subject: [PATCH 2/2] Update Option naming convention Names of new Option methods now match those used by Rust, i.e. Option.Or, Option.OrElse, Option.And, Option.AndThen. --- ZenLib.Tests/OptionTests.cs | 46 ++++++++++++++++++------------------- ZenLib/DataTypes/Option.cs | 26 +++++++++++---------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/ZenLib.Tests/OptionTests.cs b/ZenLib.Tests/OptionTests.cs index 09eaed3..41c08fe 100644 --- a/ZenLib.Tests/OptionTests.cs +++ b/ZenLib.Tests/OptionTests.cs @@ -132,12 +132,12 @@ public void TestOptionValueOrDefault() } /// - /// Test option some or default. + /// Test option OrElse. /// [TestMethod] - public void TestOptionSomeOrDefault() + public void TestOptionOrElse() { - var zf = new ZenFunction, Option, Option>((o1, o2) => o1.SomeOrDefault(() => o2)); + var zf = new ZenFunction, Option, Option>((o1, o2) => o1.OrElse(() => o2)); Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.Some(3))); Assert.AreEqual(Option.Some(3), zf.Evaluate(Option.None(), Option.Some(3))); @@ -146,8 +146,8 @@ public void TestOptionSomeOrDefault() Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.Some(3))); Assert.AreEqual(Option.Some(3), zf.Evaluate(Option.None(), Option.Some(3))); - Assert.AreEqual(Option.Some(1), Option.Some(1).SomeOrDefault(() => Option.Some(3))); - Assert.AreEqual(Option.Some(3), Option.None().SomeOrDefault(() => Option.Some(3))); + Assert.AreEqual(Option.Some(1), Option.Some(1).OrElse(() => Option.Some(3))); + Assert.AreEqual(Option.Some(3), Option.None().OrElse(() => Option.Some(3))); } /// @@ -290,12 +290,12 @@ public void TestOptionSelect() } /// - /// Test option SelectMany. + /// Test option AndThen. /// [TestMethod] - public void TestOptionSelectMany() + public void TestOptionAndThen() { - var zf = new ZenFunction, Option>(o => o.SelectMany(i => Option.Create(i + 1))); + var zf = new ZenFunction, Option>(o => o.AndThen(i => Option.Create(i + 1))); Assert.AreEqual(Option.None(), zf.Evaluate(Option.None())); Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); @@ -306,9 +306,9 @@ public void TestOptionSelectMany() Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1))); Assert.AreEqual(Option.Some(11), zf.Evaluate(Option.Some(10))); - Assert.AreEqual(Option.None(), Option.None().SelectMany(i => Option.Some(i + 1))); - Assert.AreEqual(Option.Some(2), Option.Some(1).SelectMany(i => Option.Some(i + 1))); - Assert.AreEqual(Option.Some(11), Option.Some(10).SelectMany(i => Option.Some(i + 1))); + Assert.AreEqual(Option.None(), Option.None().AndThen(i => Option.Some(i + 1))); + Assert.AreEqual(Option.Some(2), Option.Some(1).AndThen(i => Option.Some(i + 1))); + Assert.AreEqual(Option.Some(11), Option.Some(10).AndThen(i => Option.Some(i + 1))); } /// @@ -327,12 +327,12 @@ public void TestOptionDefault() } /// - /// Test option intersect. + /// Test option and. /// [TestMethod] - public void TestOptionIntersect() + public void TestOptionAnd() { - var zf = new ZenFunction, Option, Option>((o1, o2) => o1.Intersect(o2)); + var zf = new ZenFunction, Option, Option>((o1, o2) => o1.And(o2)); Assert.AreEqual(Option.None(), zf.Evaluate(Option.None(), Option.Some(1))); Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1), Option.Some(2))); @@ -343,18 +343,18 @@ public void TestOptionIntersect() Assert.AreEqual(Option.Some(2), zf.Evaluate(Option.Some(1), Option.Some(2))); Assert.AreEqual(Option.None(), zf.Evaluate(Option.Some(1), Option.None())); - Assert.AreEqual(Option.None(), Option.None().Intersect(Option.Some(1))); - Assert.AreEqual(Option.Some(2), Option.Some(1).Intersect(Option.Some(2))); - Assert.AreEqual(Option.None(), Option.Some(1).Intersect(Option.None())); + Assert.AreEqual(Option.None(), Option.None().And(Option.Some(1))); + Assert.AreEqual(Option.Some(2), Option.Some(1).And(Option.Some(2))); + Assert.AreEqual(Option.None(), Option.Some(1).And(Option.None())); } /// - /// Test option union. + /// Test option or. /// [TestMethod] - public void TestOptionUnion() + public void TestOptionOr() { - var zf = new ZenFunction, Option, Option>((o1, o2) => o1.Union(o2)); + var zf = new ZenFunction, Option, Option>((o1, o2) => o1.Or(o2)); Assert.AreEqual(Option.None(), zf.Evaluate(Option.None(), Option.None())); Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.None(), Option.Some(1))); @@ -365,9 +365,9 @@ public void TestOptionUnion() Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.None(), Option.Some(1))); Assert.AreEqual(Option.Some(1), zf.Evaluate(Option.Some(1), Option.None())); - Assert.AreEqual(Option.None(), Option.None().Union(Option.None())); - Assert.AreEqual(Option.Some(1), Option.None().Union(Option.Some(1))); - Assert.AreEqual(Option.Some(1), Option.Some(1).Union(Option.None())); + Assert.AreEqual(Option.None(), Option.None().Or(Option.None())); + Assert.AreEqual(Option.Some(1), Option.None().Or(Option.Some(1))); + Assert.AreEqual(Option.Some(1), Option.Some(1).Or(Option.None())); } } } \ No newline at end of file diff --git a/ZenLib/DataTypes/Option.cs b/ZenLib/DataTypes/Option.cs index e48d9e5..e5c4697 100644 --- a/ZenLib/DataTypes/Option.cs +++ b/ZenLib/DataTypes/Option.cs @@ -53,7 +53,7 @@ public T ValueOrDefault(T other) /// /// The default-generating function. /// An option of the underlying type. - public Option SomeOrDefault(Func> other) + public Option OrElse(Func> other) { if (this.HasValue) { @@ -76,10 +76,11 @@ public Option Select(Func function) /// /// Map a function that returns an option over an option and "flatten" the result. + /// Also known as "flat map" or "bind". /// /// The function. /// A new option with the function mapped over the value. - public Option SelectMany(Func> function) + public Option AndThen(Func> function) { Contract.AssertNotNull(function); return this.HasValue ? function(this.Value) : Option.None(); @@ -130,25 +131,25 @@ public FSeq ToSequence() } /// - /// Return the "intersection" of the option with another: + /// Return the "conjunction" of the option with another: /// an option with no value if this option has no value, /// otherwise the other option. /// /// The other option. /// An option. - public Option Intersect(Option other) + public Option And(Option other) { return !this.HasValue ? this : other; } /// - /// Return the "union" of the option with another: + /// Return the "disjunction" of the option with another: /// this option if it has a value, /// otherwise the other option. /// /// The other option. /// An option. - public Option Union(Option other) + public Option Or(Option other) { return this.HasValue ? this : other; } @@ -273,13 +274,14 @@ public static Zen> Select(this Zen> expr, Func /// The Zen expression for mapping (and projecting) over an option. + /// Also known as "flat map" or "bind". /// /// The expression. /// The function. /// The expression type. /// The function return type. /// Zen value. - public static Zen> SelectMany(this Zen> expr, + public static Zen> AndThen(this Zen> expr, Func, Zen>> function) { Contract.AssertNotNull(expr); @@ -322,7 +324,7 @@ public static Zen ValueOrDefault(this Zen> expr, Zen deflt) /// The expression. /// The function. /// Zen value. - public static Zen> SomeOrDefault(this Zen> expr, Func>> function) + public static Zen> OrElse(this Zen> expr, Func>> function) { Contract.AssertNotNull(expr); Contract.AssertNotNull(function); @@ -368,14 +370,14 @@ public static Zen> ToFSeq(this Zen> expr) } /// - /// The Zen expression for an "intersection" of two options. + /// The Zen expression for a "conjunction" of two options. /// None if the first option is None, otherwise the second value. /// /// /// /// /// - public static Zen> Intersect(this Zen> expr1, Zen> expr2) + public static Zen> And(this Zen> expr1, Zen> expr2) { Contract.AssertNotNull(expr1); Contract.AssertNotNull(expr2); @@ -384,14 +386,14 @@ public static Zen> Intersect(this Zen> expr1, Zen - /// The Zen expression for a "union" of two options. + /// The Zen expression for a "disjunction" of two options. /// The first option if it is Some, otherwise the second value. /// /// /// /// /// - public static Zen> Union(this Zen> expr1, Zen> expr2) + public static Zen> Or(this Zen> expr1, Zen> expr2) { Contract.AssertNotNull(expr1); Contract.AssertNotNull(expr2);