Skip to content

Commit

Permalink
Feature/option equality (#40)
Browse files Browse the repository at this point in the history
* Add option equality and spec

* Inc minor due to new methods

* Rm empty lines

* Rename var

* more option equality specs

* expression bodies

* optimize option GetHashCode, more tests

* - different typed null valued options have different hashcodes

---------

Co-authored-by: Alexander Wiedemann <[email protected]>
  • Loading branch information
Tyrrx and ax0l0tl authored Apr 12, 2024
1 parent 824464d commit 64aa095
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Source/FunicularSwitch.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb_aaBb" /&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb_aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb_aaBb" /&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb_aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=switchyard/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Usings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
2 changes: 1 addition & 1 deletion Source/FunicularSwitch/FunicularSwitch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<!--#region adapt versions here-->
<MajorVersion>6</MajorVersion>
<MinorAndPatchVersion>0.0</MinorAndPatchVersion>
<MinorAndPatchVersion>1.0</MinorAndPatchVersion>
<!--#endregion-->

<AssemblyVersion>$(MajorVersion).0.0</AssemblyVersion>
Expand Down
21 changes: 20 additions & 1 deletion Source/FunicularSwitch/Option.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class Option
public static Task<Option<T>> NoneAsync<T>() => Task.FromResult(Option<T>.None);
}

public readonly struct Option<T> : IEnumerable<T>
public readonly struct Option<T> : IEnumerable<T>, IEquatable<Option<T>>
{
public static readonly Option<T> None = default;

Expand Down Expand Up @@ -111,6 +111,25 @@ public async Task<TResult> Match<TResult>(Func<T, Task<TResult>> some, TResult n
public Option<TOther> Convert<TOther>() => Match(s => Option<TOther>.Some((TOther)(object)s!), Option<TOther>.None);

public override string ToString() => Match(v => v?.ToString() ?? "", () => $"None {typeof(T).BeautifulName()}");

public bool Equals(Option<T> other) => _isSome == other._isSome && EqualityComparer<T>.Default.Equals(_value, other._value);

public override bool Equals(object? obj) => obj is Option<T> other && Equals(other);

public override int GetHashCode()
{
unchecked
{
var hashCode = _isSome.GetHashCode();
hashCode = (hashCode * 397) ^ EqualityComparer<T>.Default.GetHashCode(_value);
hashCode = (hashCode * 397) ^ typeof(T).GetHashCode();
return hashCode;
}
}

public static bool operator ==(Option<T> left, Option<T> right) => left.Equals(right);

public static bool operator !=(Option<T> left, Option<T> right) => !left.Equals(right);
}

public static class OptionExtension
Expand Down
94 changes: 94 additions & 0 deletions Source/Tests/FunicularSwitch.Test/OptionSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,98 @@ from r1 in some
select x
)).Should().BeEquivalentTo(await someAsync.Map(r => r * 2));
}

[TestMethod]
public void ShouldHaveDifferentHashCodeIfBothValuesAreDifferent()
{
var hashcode1 = Some(1).GetHashCode();
var hashcode2 = Some(2).GetHashCode();
hashcode1.Should().NotBe(hashcode2);
}

[TestMethod]
public void ShouldNotBeEqualIfValuesDifferent()
{
var some1 = Some(1);
var some2 = Some(2);
some1.Equals(some2).Should().BeFalse();
(some1 == some2).Should().BeFalse();
(some1 != some2).Should().BeTrue();
}

[TestMethod]
public void ShouldHaveSameHashCodeIfBothValueAreSame()
{
var hashcode1 = Some(1).GetHashCode();
var hashcode2 = Some(1).GetHashCode();
hashcode1.Should().Be(hashcode2);
}

[TestMethod]
public void ShouldHaveDifferentHashCodeIfOneIsNone()
{
var hashcode1 = Some(1).GetHashCode();
var hashcode2 = None<int>().GetHashCode();
hashcode1.Should().NotBe(hashcode2);
}

[TestMethod]
public void ShouldBeEqualIfBothValuesAreEqual()
{
var some1 = Some(1);
var some2 = Some(1);
some1.Equals(some2).Should().BeTrue();
(some1 == some2).Should().BeTrue();
(some1 != some2).Should().BeFalse();
}

[TestMethod]
public void ShouldNotBeEqualIfOneIsNone()
{
var some = Some(1);
var none = None<int>();
some.Equals(none).Should().BeFalse();
}

[TestMethod]
public void ShouldBeEqualIfBothAreNoneOfSameType()
{
None<int>().Equals(None<int>()).Should().BeTrue();
}

[TestMethod]
public void ShouldHaveSameHashCodeIfBothAreNoneOfSameTypes()
{
None<string>().GetHashCode().Equals(None<string>().GetHashCode()).Should().BeTrue();
}

[TestMethod]
public void ShouldNotBeEqualIfBothAreNoneOfDifferentTypes()
{
// ReSharper disable once SuspiciousTypeConversion.Global
None<int>().Equals(None<string>()).Should().BeFalse();
}

[TestMethod]
public void ShouldNotHaveSameHashCodeIfBothAreNoneOfDifferentTypes()
{
// ReSharper disable once SuspiciousTypeConversion.Global
None<int>().GetHashCode().Equals(None<string>().GetHashCode()).Should().BeFalse();
}

[TestMethod]
public void NullOptionsWork()
{
var nullOption = Option<MyClass?>.Some(null);
var nullOption2 = Option<MyOtherClass?>.Some(null);
nullOption.GetHashCode().Should().NotBe(nullOption2.GetHashCode());
nullOption.Equals(Option<MyClass?>.Some(new())).Should().BeFalse();
// ReSharper disable once SuspiciousTypeConversion.Global
nullOption.Equals(nullOption2).Should().BeFalse();
nullOption.Equals(Option<MyClass?>.Some(null)).Should().BeTrue();
}

class MyClass;

class MyOtherClass;
}

0 comments on commit 64aa095

Please sign in to comment.