你是一个想了解单子背后理论的Java开发者吗?在这里你会找到一步一步的教程,帮助你理解它们。
单子是一个概念,来源于数学的一个部分,叫做范畴论,而不是一个阶级或者特质。在本文中,我将尝试解释它的结构和内部工作原理。随着...的使用可选择的在Java中,我将尝试用一种更容易理解的方式来描述所有这些。我还将实现一个基本的monad来更好地理解它们是如何工作的,并以一个简短的使用示例来展示monad相对于非monad方法的优势。
为什么要学习单子的工作原理?
首先,对我们使用的东西如何工作有一个基本的了解总是好的。如果你是一个Java开发人员,你可能会使用单子,甚至可能不知道它。这可能会让你感到惊讶,但两个最著名的Java 8特性,即溪流和可选择的是单子实现。
另外,现在函数式编程越来越流行,所以有可能我们会有更多类似的一元结构。在这种情况下,了解单子是什么以及它是如何工作的可能会变得更有价值。
回到严肃的话题...
什么是单子?
看完简介,你知道单子是范畴理论中的一个概念。在软件世界中,它可以在任何支持泛型的静态类型语言中实现为一个类或特征。此外,我们可以将它视为一个包装器,它将我们的值放在某个上下文中,并允许我们对该值执行操作,特别是返回包装在相同上下文中的值的操作。此外,我们可以以这样一种方式链接操作,即任何一步操作的输出都是下一步操作的输入。
现代编程语言中的单子示例:
- 流(Java)。
- 可选/选项(Java/Scala)。
- 要么(Scala)。
- 试试(Scala)。
- IO Monad(哈斯克尔)。
单子定律
说到单子,最后需要提到的是它们的法则。如果我们想把我们的实现看作一个真正的单子,我们必须服从它们。有三条定律:左侧标识, 正确的身份, 和结合性。 在我看来,理解它们的真正含义有点困难。
现在,在的帮助下可选的, 我将试着更详细地解释上述定律。
但首先有几个假设:
- f 从类型T到类型Optional的函数映射
- g 从R型到Optional型的函数映射
左侧标识
如果我们创建一个新的单子并将其绑定到函数,结果应该与将函数应用于值相同。
Optional<String> leftIdentity = Optional.of(x).flatMap(f);
Optional<String> mappedX = f.apply(x);
assert leftIdentity.equals(mappedX);
正确的身份
将一个单元函数绑定到一个单子的结果应该和创建一个新单子的结果一样。
Optional<Integer> rightIdentity = Optional.of(x).flatMap(Optional::of);
Optional<Integer> wrappedX = Optional.of(x);
assert rightIdentity.equals(wrappedX);
结合性
在函数应用链中,函数如何嵌套并不重要。
Optional<Long> leftSide = Optional.of(x).flatMap(f).flatMap(g);
Optional<Long> rightSide = Optional.of(x).flatMap(v -> f.apply(v).flatMap(g));
assert leftSide.equals(rightSide);
单子的创造
现在,当我们知道了基础知识,我们就可以专注于实现了。
我们首先需要一个参数化类型M,它是T类型值的包装器。我们的类型必须实现两个函数:
- 关于 (单位)用于包装我们的值,并具有以下签名M(T) .
- 平面地图 (约束)负责执行操作。这里,我们传递一个函数,该函数在我们的上下文中对值进行操作,并返回已经包装在上下文中的另一个类型。此方法应该具有以下签名M (T -> M) .
为了使它更容易理解,我将使用可选择的再一次展示在这种情况下上面的结构是什么样子。
这里,第一个条件马上得到满足,因为可选择的是一个参数化类型。该单位职能的作用是通过ofNullable和关于方法。平面地图扮演的角色约束功能。当然,在的情况下可选择的,类型边界允许我们使用比上面定义中更复杂的类型。
完成了理论,让我们实施
import java.util.function.Function;
public final class WrapperMonad<T> {
private final T value;
private WrapperMonad(T value) {
this.value = value;
}
static <T> WrapperMonad<T> of(T value) {
return new WrapperMonad<>(value);
}
<U> WrapperMonad<U> flatMap(Function<T, WrapperMonad<U>> function) {
return function.apply(value);
}
// For sake of asserting in Example
boolean valueEquals(T x) {
return value.equals(x);
}
}
瞧,单子完成了。下面详细描述一下我在这里到底做了什么。
这里到底发生了什么
我们实现的基础是参数化类与不可变字段命名为“价值”,负责存储我们的价值。然后,我们有一个私有的构造函数,这使得除了通过我们的包装方法之外,不可能以任何其他方式创建一个对象关于.
接下来,我们有两个基本的单子函数,即关于(相当于单位)和平面地图(相当于约束),这将保证我们的实现以单子定律的形式满足所需的条件。
描述完这些功能后,是时候给出一个使用示例了。这就是了。
import java.util.function.Function;
public class Example {
public static void main(String[] args) {
int x = 2;
// Task: performing operation, returning wrapped value, over the value inside the container object.
// Non-Monad
Function<Integer, Wrapper<String>> toString = i -> new Wrapper<>(i.toString());
Function<String, Wrapper<Integer>> hashCode = str -> new Wrapper<>(str.hashCode());
Wrapper<Integer> wrapper = new Wrapper<>(x);
Wrapper<String> stringifyWrapper = toString.apply(wrapper.value);
// One liner - Wrapper<Integer> hashCodedWrapper = hashCode.apply(toString.apply(x).value);
Wrapper<Integer> hashCodedWrapper = hashCode.apply(stringifyWrapper.value);
// Monad
Function<Integer, WrapperMonad<String>> toStringM = i -> WrapperMonad.of(i.toString());
Function<String, WrapperMonad<Integer>> hashCodeM = str -> WrapperMonad.of(str.hashCode());
WrapperMonad<Integer> hashCodedWrapperMonadic = WrapperMonad.of(x)
.flatMap(toStringM)
.flatMap(hashCodeM);
assert hashCodedWrapperMonadic.valueEquals(hashCodedWrapper.value);
System.out.println("Values inside wrappers are equal");
}
}
在上面的代码中,除了看到单子如何工作,我们还可以看到使用它们的一些好处。
在一元部分,所有的操作都被合并到一个单独的执行管道中,这使得代码更具声明性,更易于阅读和理解。此外,如果有一天我们决定添加错误处理逻辑,它可以很好地封装在里面关于和平面地图方法。
另一方面,在这个例子的非一元部分,我们有一个不同的包私有字段值的设计,我们需要一种从包装器外部访问值的方法,这打破了封装。就目前而言,该代码片段可读性足够好,但是您可能会注意到它不是扩展友好的。添加任何类型的异常处理都可能使它变得非常复杂。
总结
单子是一个非常有用和强大的概念,可能我们很多人在日常工作中都会用到它。我试图对其理论基础及其背后的逻辑提供清晰的描述性解释。我实现了一个定制的monad来表明它不是一个复杂的结构。在上面的例子中,我展示了monad的用法,这种方法的潜在优点,以及它与普通方法调用的不同之处。
关于Monad的常见问题
什么是单子?
单子是一个源于数学范畴理论的概念。
我为什么要关心Monad?
如果你是一名Java开发人员,你可能每天都在使用monad,但是我可以告诉你你有问题。例如,Stream和Optional是monad实现中最容易混淆的对象。此外,函数式编程变得越来越流行,所以我们可能会看到更多这样的结构。
什么是单子定律?
每个单子的实现都必须满足三个法则:左恒等式、右恒等式和结合律。
实现Monad需要什么?
要实现Monad,您需要一个参数化类型M和两个方法unit和bind。
最后
感谢阅读,点赞收藏+关注!资料已经给大家整理好了,更多的java课程学习路线,笔记,面试等架构资料,想要学习的同学可以点击获取资料