为什么在测试一个对象是否为Monad时,我们需要检查所有三个一元定律



根据我的理解,三个一元定律如下(我来自Java背景,而不是Haskell,所以请原谅我的语法):

  • 左派身份法:
Monad.of(x).flatMap(y -> f(y)) = f(x)
  • 权利认同法:
monad.flatMap(y -> Monad.of(y)) = monad
  • 联想定律:
monad.flatMap(x -> f(x)).flatMap(x -> g(x)) = monad.flatMap(x -> f(x).flatMap(x -> g(x)))

我已经读过,为了证明一个对象是一个单子,我必须证明它满足所有三个单子定律。这似乎意味着这三条定律都是必要的,而两者都不意味着另一条。然而,我很难想出一个例子,在这个例子中,一个Object可以违反每一个一元定律,而不违反其他两个。

我也无法在网上找到例子。(我发现有些人说Java Optional违反了Left Identity Law,但它使用了任何类型的null,有些人可能会认为它不是Optional的合适值。)如果我能看到一些这样的例子,可能吗?提前感谢!

编辑1:最近,我参加了一次考试,在考试中有一个例子,一个物体违反了权利认同法,并满足其他两个法律。这是:

class Counter<T> {
private final T val;
private final int count;

private Counter(T val, int count) {
this.val = val;
this.count = count;
}

public static <T> Counter<T> of(T val) {
return new Counter<>(val, 1);
}

public <R> Counter<R> map(Function<T, R> fn) {
return new Counter<>(fn.apply(this.val), this.count + 1);
}

public <R> Counter<R> flatMap(Function<T, Counter<R>> fn) {
Counter<R> tmp = fn.apply(this.val);
return new Counter<>(tmp.val, tmp.count);
}

@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (!(obj instanceof Counter<?>)) { return false; }
Counter<?> ctx = (Counter<?>) obj;
return this.val.equals(ctx.val) && this.count == ctx.count;
}
}

将Optional类型引入Java的最初动机之一是为null值提供一种替代方法,用于表达缺失值的概念。使用Optionals,缺少的值将是"Optional.empty()"值。鉴于此,将Optionals与null值结合使用通常是没有意义的(除了立即将null值转换为Optionals之外)。尽管如此,一些人坚持将两者结合起来,这导致了一些意想不到的结果。

特别是,如果选择将Optional类型解释为monad,并选择将Optional.ofNullable作为monadic单元/return,将Optional.flatMap作为monadi绑定,则monad定律将不成立。

在这个为可选类型提出的monad中重新表达的左恒等定律如下:

Optional.ofNullable(x).flatMap(f) = f

也就是说,取一个值,将其提升为Optional,然后在其上平面映射函数f,其效果与简单地将函数应用于该值相同。

如果x为null,那么我们可以取左侧,并通过在函数定义中替换Nullable和flatMap:来减少它

Optional.ofNullable(x).flatMap(f)
=> Optional.empty().flatMap(f)
=> Optional.empty()

(其中=>表示减少到,或相当于

然而,函数f在直接应用于x(即null)时不必返回Optional.empty()。一个简单的例子是以下函数:

Optional<String> stringify(Object obj) {
if (obj == null) {
return Optional.of("NULL");
} else {
return Optional.of(obj.toString());
}

如果我们将左恒等定律的RHS中的f作为stringify函数,并将其应用于null,则我们得到一个包裹字符串"的可选值;如果为"NULL";,也就是说,它与该定律的LHS不匹配,因此该定律不成立。

如果不使用Optional.ofNullable作为一元单元,而是使用Optional.of,则此问题就会消失,因为后一个函数不允许使用null值。

转到您提供的Counter代码,这有点奇怪。

首先,flatMap的实现是创建`tmp对象的冗余副本。由于这并没有真正的效果,该方法可以简单地重写如下:

公共计数器平面图(函数<T,计数器>fn){返回fn.apply(this.val);}

其次,由于上下文是单子,我们只需要关注a)组成类的字段,2)它的of方法和3)flatMap方法,因为这些是唯一与单子行为相关的元素。这些方法永远不会改变计数字段,所以我们可以忽略它。因此,我们可以有效地重写类如下:

类计数器{私人最终估价;

private Counter(T val) {
this.val = val;
}
public static <T> Counter<T> of(T val) {
return new Counter<>(val);
}
public <R> Counter<R> flatMap(Function<T, Counter<R>> fn) {
return fn.apply(this.val);
}
}

事实上,这就是同一性monad,这三条定律确实适用。

然而,如果我们回到原始代码并查看map方法,那么我们可以看到它在每次调用映射时都会增加count字段。所有Monad类型也是Functor,Functor需要有一个方法(通常称为map),该方法也遵循一条规则,即通过两次调用map在Functor值上链接两个函数,与通过一次调用map组合函数和应用组合结果相同。Counter类中的map实现将最大程度地打破该规则,因为count字段将根据调用map的次数而不同。

这可以说意味着类不能是monad,假设您将map方法解释为Functormap函数。这是一个假设,我希望任何关于该代码的问题都能明确说明这一点,否则该问题将缺少能够回答它所需的关键信息

我想作者想不出一个更好的monad例子来打破一个规则而不是其他规则,所以他们只是在Java中实现了Identity monad,然后添加了一个有缺陷的map实现。如果你真的想了解monad,那么我建议你找一个更好的信息来源。

最新更新