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 是否准确,为什么或为什么不准确?

【问题讨论】:

NonEmptyOption(或 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&lt;T&gt; 也没有 monad 实例,因为无法构造嵌套类型 Nullable&lt;Nullable&lt;T&gt;&gt;,因此无法实现 join

【讨论】:

我不知道嵌套的 nullables 是不允许的,但这似乎是语言的一个特殊(故意)怪癖,而不是概念类型的基本属性。你可以实现SelectMany(monadic bind),所以情况还不清楚。通常,如果你有 bind,你可以实现join,或者相反,但这里不是,因为 C# 的特殊情况。不过,对于所有实际情况,我认为 Nullable&lt;T&gt; 是一个单子。 @MarkSeemann - 你可以实现SelectMany,但不能实现pure,因为pure(null)必须返回null而不是HasValue: true, Value: null。这意味着左同一性法则被打破,因为 pure(null).SelectMany(_ =&gt; 3)null 而不是 3 根据需要。但是,是的,出于实际目的,将 Nullable&lt;T&gt; 视为具有 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&lt;T&gt;)。

在 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&lt;T&gt; 做同样的事情,但我会把它作为练习 ?

您也可以使用 C# 8 的新 可空引用类型 语言特性来做到这一点,但我还没有尝试过。

【讨论】:

所以我想如果我要使用正确的Maybes 做我想做的事情,它将涉及每个潜在的空字符串是Maybe&lt;string&gt; 并做一些嵌套的Match或后备调用。 @Bondolin 您不必处理嵌套的 Maybes。这就是SelectMany 的用途;它会随着您的移动而变平。

以上是关于String.IsNullOrEmpty 单子的主要内容,如果未能解决你的问题,请参考以下文章

C# 使用 String.IsNullOrEmpty 切换

转载:string.IsNullOrEmpty和string.IsNullOrWhiteSpace方法的区别

string.IsNullOrEmpty和string.IsNullOrWhiteSpace方法的区别

string.IsNullOrEmpty 和 string.IsNullOrWhiteSpace 为空字符串返回 false

String.IsNullOrEmpty()和String.IsNullOrWhiteSpace()

使用 String.IsNullOrEmpty(string) 和 Nhibernate 创建动态 Linq 表达式