From 8b4aae1d633edcaabb3535352bdcf66c4d2a5dab Mon Sep 17 00:00:00 2001 From: sanchime Date: Mon, 9 Jan 2023 21:12:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4Option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.idea.Sanchime/.idea/.gitignore | 13 +++++ .idea/.idea.Sanchime/.idea/.name | 1 + .idea/.idea.Sanchime/.idea/encodings.xml | 4 ++ .idea/.idea.Sanchime/.idea/indexLayout.xml | 8 +++ .idea/.idea.Sanchime/.idea/vcs.xml | 6 ++ Sanchime.Functional/Extensions/Dictionary.cs | 2 +- Sanchime.Functional/Extensions/Enum.cs | 4 +- Sanchime.Functional/Extensions/IEnumerable.cs | 24 ++++---- Sanchime.Functional/Extensions/Nullable.cs | 4 +- Sanchime.Functional/Extensions/Option.cs | 46 ++++++--------- .../Products/Eithers/IEither.cs | 2 +- Sanchime.Functional/Products/IApplicative.cs | 16 ------ Sanchime.Functional/Products/ICategory.cs | 6 -- Sanchime.Functional/Products/IFunctor.cs | 24 -------- Sanchime.Functional/Products/IMonad.cs | 10 ---- .../Products/Options/IOption.cs | 8 ++- Sanchime.Functional/Products/Options/None.cs | 6 +- .../Products/Options/NoneFunctor.cs | 12 ---- .../Products/Options/Option.cs | 32 +++++------ .../Products/Options/OptionFunctor.cs | 27 ++++++++- .../Products/Options/OptionMonad.cs | 16 ++++++ Sanchime.Functional/Products/Options/Some.cs | 7 +-- Sanchime.Test/Program.cs | 57 ++++++++++++------- Sanchime.Toolkits/Basic.cs | 2 +- Sanchime.sln.DotSettings.user | 2 + 25 files changed, 173 insertions(+), 166 deletions(-) create mode 100644 .idea/.idea.Sanchime/.idea/.gitignore create mode 100644 .idea/.idea.Sanchime/.idea/.name create mode 100644 .idea/.idea.Sanchime/.idea/encodings.xml create mode 100644 .idea/.idea.Sanchime/.idea/indexLayout.xml create mode 100644 .idea/.idea.Sanchime/.idea/vcs.xml delete mode 100644 Sanchime.Functional/Products/IApplicative.cs delete mode 100644 Sanchime.Functional/Products/ICategory.cs delete mode 100644 Sanchime.Functional/Products/IFunctor.cs delete mode 100644 Sanchime.Functional/Products/IMonad.cs delete mode 100644 Sanchime.Functional/Products/Options/NoneFunctor.cs create mode 100644 Sanchime.Functional/Products/Options/OptionMonad.cs create mode 100644 Sanchime.sln.DotSettings.user diff --git a/.idea/.idea.Sanchime/.idea/.gitignore b/.idea/.idea.Sanchime/.idea/.gitignore new file mode 100644 index 0000000..999f71a --- /dev/null +++ b/.idea/.idea.Sanchime/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/projectSettingsUpdater.xml +/modules.xml +/.idea.Sanchime.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.Sanchime/.idea/.name b/.idea/.idea.Sanchime/.idea/.name new file mode 100644 index 0000000..104cc73 --- /dev/null +++ b/.idea/.idea.Sanchime/.idea/.name @@ -0,0 +1 @@ +Sanchime \ No newline at end of file diff --git a/.idea/.idea.Sanchime/.idea/encodings.xml b/.idea/.idea.Sanchime/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.Sanchime/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.Sanchime/.idea/indexLayout.xml b/.idea/.idea.Sanchime/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Sanchime/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Sanchime/.idea/vcs.xml b/.idea/.idea.Sanchime/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.Sanchime/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sanchime.Functional/Extensions/Dictionary.cs b/Sanchime.Functional/Extensions/Dictionary.cs index 4c09738..6bd8513 100644 --- a/Sanchime.Functional/Extensions/Dictionary.cs +++ b/Sanchime.Functional/Extensions/Dictionary.cs @@ -6,5 +6,5 @@ namespace Sanchime.Functional.Extensions; public static class DictionaryExtension { public static Option Lookup(this Dictionary dictionary, K key) where K: notnull - => dictionary.TryGetValue(key, out T? value) ? Option.Some(value) : Option.None; + => dictionary.TryGetValue(key, out T? value) ? Optional.Some(value) : Optional.None; } diff --git a/Sanchime.Functional/Extensions/Enum.cs b/Sanchime.Functional/Extensions/Enum.cs index 28fd95d..d06babc 100644 --- a/Sanchime.Functional/Extensions/Enum.cs +++ b/Sanchime.Functional/Extensions/Enum.cs @@ -3,8 +3,8 @@ namespace Sanchime.Functional.Extensions; public static class EnumExtension { /// - /// 将字符串转换为相应的枚举值的 public static Option Parse(this string @this) where T : struct - => Enum.TryParse(@this, out T val) ? Option.Some(val) : Option.None; + => Enum.TryParse(@this, out T val) ? Optional.Some(val) : Optional.None; } diff --git a/Sanchime.Functional/Extensions/IEnumerable.cs b/Sanchime.Functional/Extensions/IEnumerable.cs index 2400e68..857de0b 100644 --- a/Sanchime.Functional/Extensions/IEnumerable.cs +++ b/Sanchime.Functional/Extensions/IEnumerable.cs @@ -11,7 +11,7 @@ public static class IEnumerableExtension public static Func> Return() => t => List(t); /// - /// 将序列内第一条元素转换为 + /// 将序列内第一条元素转换为 /// /// /// @@ -20,14 +20,14 @@ public static class IEnumerableExtension { if (source is null) { - return Option.None; + return Optional.None; } var first = source.FirstOrDefault(); - return first is not null ? Option.Some(first) : Option.None; + return first is not null ? Optional.Some(first) : Optional.None; } /// - /// 给定谓词,查询序列内第一条数据并将其转换为 + /// 给定谓词,查询序列内第一条数据并将其转换为 /// /// 数据源 /// 谓词 @@ -37,14 +37,14 @@ public static class IEnumerableExtension { if (source is null) { - return Option.None; + return Optional.None; } var first = source.Where(predicate).FirstOrDefault(); - return first is not null ? Option.Some(first) : Option.None; + return first is not null ? Optional.Some(first) : Optional.None; } /// - /// 将序列内最后一个元素转换为 + /// 将序列内最后一个元素转换为 /// /// /// @@ -53,14 +53,14 @@ public static class IEnumerableExtension { if (source is null) { - return Option.None; + return Optional.None; } var tail = source.LastOrDefault(); - return tail is not null ? Option.Some(tail) : Option.None; + return tail is not null ? Optional.Some(tail) : Optional.None; } /// - /// 给定谓词,查询序列内最后一条数据并将其转换为 + /// 给定谓词,查询序列内最后一条数据并将其转换为 /// /// 数据源 /// 谓词 @@ -70,10 +70,10 @@ public static class IEnumerableExtension { if (source is null) { - return Option.None; + return Optional.None; } var last = source.Where(predicate).LastOrDefault(); - return last is not null ? Option.Some(last) : Option.None; + return last is not null ? Optional.Some(last) : Optional.None; } public static R Match(this IEnumerable source, Func Empty, Func, R> Otherwise) diff --git a/Sanchime.Functional/Extensions/Nullable.cs b/Sanchime.Functional/Extensions/Nullable.cs index 9ff820f..0328d05 100644 --- a/Sanchime.Functional/Extensions/Nullable.cs +++ b/Sanchime.Functional/Extensions/Nullable.cs @@ -3,8 +3,8 @@ namespace Sanchime.Functional.Extensions; public static class NullableExtension { /// - /// 将值转换为 + /// 将值转换为 /// public static Option ToOption(this Nullable @this) where T : struct - => @this.HasValue ? Option.Some(@this.Value) : Option.None; + => @this.HasValue ? Optional.Some(@this.Value) : Optional.None; } diff --git a/Sanchime.Functional/Extensions/Option.cs b/Sanchime.Functional/Extensions/Option.cs index d802293..63bcaf9 100644 --- a/Sanchime.Functional/Extensions/Option.cs +++ b/Sanchime.Functional/Extensions/Option.cs @@ -2,8 +2,7 @@ namespace Sanchime.Functional.Extensions; public static class OptionExtension { - public static Unit Match(this Option option, Action None, Action Some) - => option.Match(None.ToFunc(), Some.ToFunc()); + #region 函子: 同一范畴内不同对象之间的态射 @@ -13,27 +12,18 @@ public static class OptionExtension // public static Option Map(this Some some, Func mapping) // => Option.Some(mapping(some.Value)); - public static Option Map(this Option option, Func mapping) - => option.Match(() => Option.None, (val) => Option.Some(mapping(val))); + // public static Option Map(this Option option, Func mapping) + // => option.Match(() => Option.None, (val) => Option.Some(mapping(val))); - // public static Option> Map(this Option option, Func mapping) + // public static IOption Map(this Option option, Func mapping) // => option.Map(mapping.Curry()); - // public static Option> Map(this Option option, Func mapping) + // public static IOption Map(this Option option, Func mapping) // => option.Map(mapping.CurryFirst()); #endregion #region 单子: 自函子范畴上的幺半群 - /// - /// Return自然变换: 将上下文无关的值导入至上下文有关的世界 - /// - /// - /// - /// - public static Option Return(T value) - => value is null ? Option.None : Option.Some(value); - public static Option Bind(this Option option, Func> binding) => option.Match(() => new Option(), binding); @@ -46,12 +36,12 @@ public static class OptionExtension // #region 应用函子 - // public static Option Apply(this Option> option, Option value) - // => option.Match( - // None: () => Option.None, - // Some: (apply) => value.Match( - // None: () => Option.None, - // Some: (val) => Option.Some(apply(val)))); + public static Option Apply(this Option> option, Option value) + => option.Match( + None: () => Optional.None, + Some: (apply) => value.Match( + None: () => Optional.None, + Some: (val) => Optional.Some(apply(val)))); // public static Option> Apply(this Option> option, Option value) // => option.Map(CurryingExtension.Curry).Apply(value); @@ -95,7 +85,7 @@ public static class OptionExtension => left.Match(() => right(), (_) => left); /// - /// 将 + /// 将 /// /// /// @@ -111,18 +101,18 @@ public static class OptionExtension #region Linq式 - // public static Option Select(this Option source, Func mapping) - // => source.Map(mapping); + public static IOption Select(this Option source, Func mapping) + => source.Map(mapping); public static Option Where(this Option source, Func predicate) - => source.Match(() => Option.None, (val) => predicate(val) ? source : Option.None); + => source.Match(() => Optional.None, (val) => predicate(val) ? source : Optional.None); public static Option SelectMany(this Option source, Func> binding, Func project) => source.Match( - None: () => Option.None, + None: () => Optional.None, Some: (val) => binding(val).Match( - None: () => Option.None, - Some: (res) => Option.Some(project(val, res)))); + None: () => Optional.None, + Some: (res) => Optional.Some(project(val, res)))); #endregion diff --git a/Sanchime.Functional/Products/Eithers/IEither.cs b/Sanchime.Functional/Products/Eithers/IEither.cs index 5d96db9..7297271 100644 --- a/Sanchime.Functional/Products/Eithers/IEither.cs +++ b/Sanchime.Functional/Products/Eithers/IEither.cs @@ -10,7 +10,7 @@ public interface IEither : IEither } -public interface IEither : IFunctor +public interface IEither { } \ No newline at end of file diff --git a/Sanchime.Functional/Products/IApplicative.cs b/Sanchime.Functional/Products/IApplicative.cs deleted file mode 100644 index 79fd63e..0000000 --- a/Sanchime.Functional/Products/IApplicative.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Sanchime.Functional.Products; - -/// -/// 应用函子 -/// -/// -public interface IApplicative : IApplicative, IFunctor - where TCategory : ICategory -{ -} - - -public interface IApplicative : IFunctor -{ - -} \ No newline at end of file diff --git a/Sanchime.Functional/Products/ICategory.cs b/Sanchime.Functional/Products/ICategory.cs deleted file mode 100644 index 339204b..0000000 --- a/Sanchime.Functional/Products/ICategory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Sanchime.Functional.Products; - -public interface ICategory -{ - -} diff --git a/Sanchime.Functional/Products/IFunctor.cs b/Sanchime.Functional/Products/IFunctor.cs deleted file mode 100644 index c17ebf1..0000000 --- a/Sanchime.Functional/Products/IFunctor.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Sanchime.Functional.Products; - -/// -/// 函子 -/// 同一范畴内不同对象之间的映射 -/// -/// -public interface IFunctor : IFunctor - where TCategory : ICategory -{ - TCategory Map(Func mapping); -} - -public interface IFunctor : IFunctor - where TCategory : ICategory -{ - TCategory Map(Func mapping); -} - -public interface IFunctor : ICategory -{ - public TResult Map(Func mapping) - => mapping(this); -} \ No newline at end of file diff --git a/Sanchime.Functional/Products/IMonad.cs b/Sanchime.Functional/Products/IMonad.cs deleted file mode 100644 index 8debbde..0000000 --- a/Sanchime.Functional/Products/IMonad.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Sanchime.Functional.Products; - -/// -/// 单子 -/// -/// -public interface IMonad : IApplicative - where TCategory : ICategory -{ -} diff --git a/Sanchime.Functional/Products/Options/IOption.cs b/Sanchime.Functional/Products/Options/IOption.cs index 57e4fe1..21ee327 100644 --- a/Sanchime.Functional/Products/Options/IOption.cs +++ b/Sanchime.Functional/Products/Options/IOption.cs @@ -5,12 +5,16 @@ using System.Threading.Tasks; namespace Sanchime.Functional.Products; -public interface IOption : IFunctor +public interface IOption { bool IsSome { get; } } -public interface IOption : IOption, IFunctor +/// +/// 选项 +/// +/// 选项内部维护的值 +public interface IOption : IOption { TValue Value { get; } } diff --git a/Sanchime.Functional/Products/Options/None.cs b/Sanchime.Functional/Products/Options/None.cs index 5d9409f..556ca45 100644 --- a/Sanchime.Functional/Products/Options/None.cs +++ b/Sanchime.Functional/Products/Options/None.cs @@ -1,10 +1,14 @@ namespace Sanchime.Functional.Products; /// -/// 状态 +/// 状态 /// public readonly partial struct None : IOption { + /// + /// 静态构造形成单例 + /// + static None() { } public bool IsSome => false; internal static readonly None Default = new(); } diff --git a/Sanchime.Functional/Products/Options/NoneFunctor.cs b/Sanchime.Functional/Products/Options/NoneFunctor.cs deleted file mode 100644 index 731498e..0000000 --- a/Sanchime.Functional/Products/Options/NoneFunctor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sanchime.Functional.Products; - -public readonly partial struct None : IFunctor -{ - public None Map(Func mapping) - => Option.None; -} \ No newline at end of file diff --git a/Sanchime.Functional/Products/Options/Option.cs b/Sanchime.Functional/Products/Options/Option.cs index 13b89ed..2aa665a 100644 --- a/Sanchime.Functional/Products/Options/Option.cs +++ b/Sanchime.Functional/Products/Options/Option.cs @@ -1,6 +1,8 @@ +using Sanchime.Functional.Extensions; + namespace Sanchime.Functional.Products; -public static class Option +public static class Optional { public static None None => None.Default; public static Option Some(TValue value) => new Some(value); @@ -18,39 +20,39 @@ public readonly partial struct Option : IOption, IEquatable (_isSome, _value) = (true, value ?? throw new ArgumentNullException(nameof(value))); /// - /// 将转换为 + /// 将转换为 /// /// public static implicit operator Option(None _) => new(); /// - /// 将转换为 + /// 将转换为 /// /// public static implicit operator Option(Some some) => new(some.Value); /// - /// 将常规值提升至 + /// 将常规值提升至 /// /// public static implicit operator Option(TValue value) - => value is null ? Option.None : Option.Some(value); - - public bool IsSome => this.Match(() => false, (_) => true); + => value is null ? Optional.None : Optional.Some(value); + + public bool IsSome => _isSome; /// - /// 获取内部的值,如果为则抛出异常 + /// 获取内部的值,如果为则抛出异常 /// /// public TValue ValueUnsafe => this.Match(() => throw new InvalidOperationException(), val => val); /// - /// 获取内部的值,如果为则返回默认值 + /// 获取内部的值,如果为则返回默认值 /// /// - public TValue Value => this.Match(() => default!, val => val); + public TValue Value => _isNone ? default! : _value; /// /// 转换为序列 @@ -60,15 +62,7 @@ public readonly partial struct Option : IOption, IEquatable - /// 接收两个函数,根据的状态进行求值 - /// - /// - /// None状态 - /// Some状态 - /// - public TResult Match(Func None, Func Some) - => _isSome ? Some(_value) : None(); + public bool Equals(Option other) => this._isSome == other._isSome diff --git a/Sanchime.Functional/Products/Options/OptionFunctor.cs b/Sanchime.Functional/Products/Options/OptionFunctor.cs index eeb66bb..c9b2c55 100644 --- a/Sanchime.Functional/Products/Options/OptionFunctor.cs +++ b/Sanchime.Functional/Products/Options/OptionFunctor.cs @@ -2,18 +2,39 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Sanchime.Functional.Extensions; namespace Sanchime.Functional.Products; -public readonly partial struct Option +public static class OptionFunctor { + /// + /// 接收两个函数,根据的状态进行求值 + /// + /// + /// None状态 + /// Some状态 + /// + public static TResult Match(this IOption option, Func None, Func Some) + => option.IsSome ? Some(option.Value) : None(); + + public static Unit Match(this IOption option, Action None, Action Some) + => option.Match(None.ToFunc(), Some.ToFunc()); + /// /// 映射 /// + /// /// + /// /// /// - public IOption Map(Func mapping) - => this.Match(() => Option.None, value => Option.Some(mapping(value))); + public static IOption Map(this IOption option, Func mapping) + => option.Match(() => Optional.None, value => Optional.Some(mapping(value))); + public static IOption> Map(this IOption option,Func mapping) + => option.Map(mapping.Curry()); + + public static IOption> Map(this IOption option, Func mapping) + => option.Map(mapping.CurryFirst()); } \ No newline at end of file diff --git a/Sanchime.Functional/Products/Options/OptionMonad.cs b/Sanchime.Functional/Products/Options/OptionMonad.cs new file mode 100644 index 0000000..3e3492d --- /dev/null +++ b/Sanchime.Functional/Products/Options/OptionMonad.cs @@ -0,0 +1,16 @@ +namespace Sanchime.Functional.Products; + +public readonly partial struct Option +{ + /// + /// Return自然变换: 将上下文无关的值导入至上下文有关的世界 + /// + /// + /// + /// + public static IOption Return(T value) + => value is null ? Optional.None : Optional.Some(value); + + public static IOption Return(TValue value) + => Return(value); +} \ No newline at end of file diff --git a/Sanchime.Functional/Products/Options/Some.cs b/Sanchime.Functional/Products/Options/Some.cs index c6efb1a..450ee87 100644 --- a/Sanchime.Functional/Products/Options/Some.cs +++ b/Sanchime.Functional/Products/Options/Some.cs @@ -1,16 +1,15 @@ namespace Sanchime.Functional.Products; /// -/// 状态 +/// 状态 /// /// -public readonly struct Some : IOption +public readonly struct Some : IOption { public bool IsSome => Value is not null; public TValue Value { get; } internal Some(TValue value) => Value = value ?? throw new ArgumentNullException(nameof(value), "不能在Some状态中包装null值, 应该使用None"); - public IOption Map(Func mapping) - => Option.Some(mapping(this.Value)); + => Optional.Some(mapping(this.Value)); } diff --git a/Sanchime.Test/Program.cs b/Sanchime.Test/Program.cs index 429c5eb..4f8fe00 100644 --- a/Sanchime.Test/Program.cs +++ b/Sanchime.Test/Program.cs @@ -3,38 +3,51 @@ using Sanchime.Functional.Extensions; using Sanchime.Toolkits; -void foo(Option option) +try { - var res = option.Map(x => x + 2); - res.WriteLine(); -} -"预计打印Some(12)".WriteLine(); -foo(10); -"预计打印None".WriteLine(); -foo(Option.None); + void foo(Option option) + { + var res = option.Map(x => x + 2); + res.WriteLine(); + } -"预计打印Char".WriteLine(); -Option.Some(1) - .Map(x => (float)x + 1.2) - .Map(x => x.ToString()) - .Map(x => x.GetType().Name) - .ForEach(x => x.WriteLine()); + "预计打印Some(12)".WriteLine(); + foo(10); + "预计打印None".WriteLine(); + foo(Optional.None); + + "预计打印Some(String)".WriteLine(); + Optional.Some(1) + .Map(x => x + 1.2) + .Map(x => x.ToString()) + .Map(x => x.GetType().Name) + .WriteLine(); // 测试Option的Bind -var parse = (string s) => Int32.TryParse(s, out int i) ? Option.Some(i) : Option.None; -var foo1 = (string s) => s.Pipe(parse).Bind(Age.Of); - -foo1("111").WriteLine(); -foo1("aaa").WriteLine(); -foo1("123").WriteLine(); + var parse = (string s) => Int32.TryParse(s, out int i) ? Optional.Some(i) : Optional.None; + var foo1 = (string s) => s.Pipe(parse).Bind(Age.Of); + "预计打印Some(111)".WriteLine(); + foo1("111").WriteLine(); + "预计打印None".WriteLine(); + foo1("aaa").WriteLine(); + "预计打印Some(123)".WriteLine(); + foo1("123").WriteLine(); // 管道 -foo1("1ab").Pipe(x => Console.WriteLine(x)); + "预计打印None".WriteLine(); + foo1("1ab").Pipe(x => x.WriteLine()); +} +catch (Exception ex) +{ + ex.Message.WriteLine(); + ex.StackTrace.WriteLine(); +} + public struct Age { private int _value; public static Option Of(int age) - => IsValid(age) ? Option.Some(new Age(age)) : Option.None; + => IsValid(age) ? Optional.Some(new Age(age)) : Optional.None; private Age(int age) { diff --git a/Sanchime.Toolkits/Basic.cs b/Sanchime.Toolkits/Basic.cs index 217aed1..a15e98c 100644 --- a/Sanchime.Toolkits/Basic.cs +++ b/Sanchime.Toolkits/Basic.cs @@ -5,7 +5,7 @@ public static class Basic { public static void WriteLine(this T @this) { - Debug.WriteLine(@this); + Console.WriteLine(@this); } public static void WriteLine(this T @this, Func func) diff --git a/Sanchime.sln.DotSettings.user b/Sanchime.sln.DotSettings.user new file mode 100644 index 0000000..1460b21 --- /dev/null +++ b/Sanchime.sln.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file