String.IsNullOrEmpty 单子
Posted
技术标签:
【中文标题】String.IsNullOrEmpty 单子【英文标题】:String.IsNullOrEmpty Monad 【发布时间】:2020-11-03 19:40:57 【问题描述】:我最近涉足函数式编程的迷人世界,主要是因为获得了 React 等 FP 平台的经验以及阅读了 https://blog.ploeh.dk/ 之类的博客。作为一个主要的命令式程序员,这是一个有趣的转变,但我仍在努力让自己的脚湿透。
我有点厌倦了这样使用string.IsNullOrEmpty
。很多时候,我发现自己在代码中乱扔了诸如
_ = string.IsNullOrEmpty(str) ? "default text here" : str;
这并没有那么糟糕,但是说我想将一堆选项链接到那个空值之外,例如
_ = string.IsNullOrEmpty(str) ? (
util.TryGrabbingMeAnother() ??
"default text here") : str;
哎呀。我宁愿有这样的东西--
_ = monad.NonEmptyOrNull(str) ??
util.TryGrabbingMeAnother() ??
"default text here";
如示例所示,我正在使用我称为 monad 的函数来帮助将 string.IsNullOrEmpty
简化为可空链操作:
public string NonEmptyOrNull(string source) =>
string.IsNullOrEmpty(source) ? null : source;
我的问题是,这是正确的术语吗?我知道Nullable<T>
可以被认为是一个单子(见Can Nullable be used as a functor in C#? 和Monad in plain English? (For the OOP programmer with no FP background))。这些材料是很好的参考资料,但我仍然没有足够的直觉掌握这个主题,无法知道我在这里是否只是在混淆或不一致。例如,我知道 monad 应该像上面那样启用函数链接,但它们也是“类型放大器”——所以我的小例子似乎 表现 像一个启用链接的 monad,但它似乎将 null/empty 转换为 null 是 reduction 而不是放大,所以我质疑这是否真的 is 一个 monad。那么对于这个特定的应用程序,有更多 FP 经验的人能否告诉我将 NonEmptyOrNull
称为 monad 是否准确,为什么或为什么不准确?
【问题讨论】:
NonEmpty
和 Option
(或 Nullable
)都是 monad,因此您可以将它们组合起来以获得所需的类型。一般来说,你不能组成 monad,但你可以编写一个函数,通过另一个 monad 的语义来扩展一个 monad。这种方法被称为单子变换器。无论如何,我建议你先学习仿函数和应用仿函数,因为 monad 也是仿函数,而后者决定了它们大约 80% 的语义。如果您有兴趣,我一直在编写 FP 课程。
【参考方案1】:
我相信这通常在 FP 范例中解决,比验证 null
提前一步。 str
值绝不能是 null
。相反,原始方法必须返回一个空集合。这样,方法的链接就不必验证 null。由于没有要操作的元素,因此不会执行下一个操作
您可以找到多个参考资料。互联网上与此相关。 https://www.informit.com/articles/article.aspx?p=2133373&seqNum=5 是我可以快速抓住的一个
我从 Pluralsight 的 Zoran Horvat 课程中学到了这一点。如果您有访问权限,请检查一下。课程名称为“ .NET 中的战术设计模式:控制流”,模块为“空对象和特例模式”
考虑到对 FP 的兴趣,Zoran Horvat 还提供了其他有助于转换或使 OO 代码更实用的课程。在这里回复我很兴奋,因为最近我也在研究 FP。祝你好运!
【讨论】:
【参考方案2】:单子是由以下组成的三元组:
单参数类型构造函数M
unit
类型的函数 a -> M a
join
类型的函数 M (M a) -> a
满足单子法则。
类型构造函数是一个类型级别的函数,它接受多个类型参数并返回一个类型。 C# 没有直接具有此功能,但在编码 monad 时,您需要一个单参数泛型类型,例如List<T>
、Task<T>
等。因此,对于某些泛型类型M
,您需要两个函数来从单个值构造泛型类型的实例,一个“展平”该类型的嵌套实例。例如List<T>
:
public static List<T> unit<T>(T value) return new List<T> value ;
public static List<T> join<T>(List<List<T>> l) return l.SelectMany(l => l);
从这个定义你可以看出单个函数不能满足monad的定义,所以你的例子不是monad的例子。
根据这个定义,Nullable<T>
也没有 monad 实例,因为无法构造嵌套类型 Nullable<Nullable<T>>
,因此无法实现 join
。
【讨论】:
我不知道嵌套的 nullables 是不允许的,但这似乎是语言的一个特殊(故意)怪癖,而不是概念类型的基本属性。你可以实现SelectMany
(monadic bind),所以情况还不清楚。通常,如果你有 bind,你可以实现join
,或者相反,但这里不是,因为 C# 的特殊情况。不过,对于所有实际情况,我认为 Nullable<T>
是一个单子。
@MarkSeemann - 你可以实现SelectMany
,但不能实现pure
,因为pure(null)
必须返回null
而不是HasValue: true, Value: null
。这意味着左同一性法则被打破,因为 pure(null).SelectMany(_ => 3)
是 null
而不是 3
根据需要。但是,是的,出于实际目的,将 Nullable<T>
视为具有 monad 实例可能是可以的。【参考方案3】:
这更像是一个 filter 操作。在 C# 中,您通常将其称为 Where
。如果我们更明确地区分缺失值和填充值,可能更容易看出,我们可以使用the Maybe container:
public static Maybe<T> Where<T>(
this Maybe<T> source,
Func<T, bool> predicate)
return source.SelectMany(x => predicate(x) ? x.ToMaybe() : Maybe.Empty<T>());
只有少数containers 支持过滤。最常见的两个是Maybe
(又名Option
)和各种集合(即IEnumerable<T>
)。
在 Haskell(它有比 C# 更强大的类型系统)this is enabled 通过一个名为 MonadPlus
的类,但我认为类型类 Alternative
实际上应该足以实现过滤。 Alternative
is described 作为应用函子上的一个 monoid。 不过,我不确定这是否特别有用。
使用上述Where
方法,您可以像这样通过IsNullOrEmpty
之类的检查将Maybe
值线程化:
var m = "foo".ToMaybe();
var inspected = m.Where(s => !string.IsNullOrEmpty(s));
这将使m
原封不动地通过,而以下则不会:
var m = "".ToMaybe();
var inspected = m.Where(s => !string.IsNullOrEmpty(s));
你可以对 Nullable<T>
做同样的事情,但我会把它作为练习 ?
您也可以使用 C# 8 的新 可空引用类型 语言特性来做到这一点,但我还没有尝试过。
【讨论】:
所以我想如果我要使用正确的Maybe
s 做我想做的事情,它将涉及每个潜在的空字符串是Maybe<string>
并做一些嵌套的Match
或后备调用。
@Bondolin 您不必处理嵌套的 Maybes。这就是SelectMany
的用途;它会随着您的移动而变平。以上是关于String.IsNullOrEmpty 单子的主要内容,如果未能解决你的问题,请参考以下文章
转载:string.IsNullOrEmpty和string.IsNullOrWhiteSpace方法的区别
string.IsNullOrEmpty和string.IsNullOrWhiteSpace方法的区别
string.IsNullOrEmpty 和 string.IsNullOrWhiteSpace 为空字符串返回 false