你是一个想知道函子背后理论的Java开发者吗?在这里你会找到一步一步的教程,帮助你理解它们。
一般来说,一个函子是一个源于数学的概念,确切的说是源于数学中一个叫做范畴论的部分。在我的第一篇文章,我详细描述了一个类似的概念,称为单子。 在这里,我想继续这一思路,所以我会尝试给你一些更多的见解函子—它们是什么,它们如何工作,以及它们背后的理论是什么。我还将实现一个简单的仿函数来更好地理解它们是如何工作的,以及为什么使用它们可能是一个更清晰的解决方案。
为什么还要关心函子?
首先,函子可能是最简单的众所周知的函数容器,理解它们的机制有助于处理更复杂的容器,如单子或应用程序。
其次,这是一个非常方便的概念,尤其是当您需要对容器内的一些值执行操作时。
最后,与在Java中有相当多对应物的单子不同,函子缺少一个很好的内置对等物,至少就我所知是这样,所以了解这个概念也许有一天会派上用场。它可以让你的生活更轻松。
什么是函子?
正如我上面提到的,一个函子是范畴理论中的一个概念,表示两个范畴之间的映射。在软件中,函子可以被视为一个util类,它允许我们对包装在某些上下文中的值执行映射操作。尽管使用一个简单的接口很容易实现,但我们必须记住,要诚实地称我们的代码段为函子,我们必须满足适用于范畴理论中的函数的法则——下面将详细介绍。
此外,与第一篇文章中的单子相反,在爪哇和其他现代编程语言。也许最好的对应可以在斯卡拉调用的库猫.
函子定律
随着大部分概念从数学转移到编程,函子背后也有了一些理论背景。在函子的情况下,这种背景的一部分是它们的定律,即身份和结合性。下面,我将使用一个参考功能我自己创造的阶级。
关于关联性部分中使用的函数的假设:
- f 是从类型映射的函数 T打字R
- g是从类型映射的函数R 打字U
身份:
用identity函数映射函子内部的值应该总是返回一个不变的值。
ReferentialFunctor<Integer> identity = new ReferentialFunctor<>(x).map(Function.identity());
assert identity.valueEquals(x);
结合性:
这个定律与单子的情况相同,所以它表明在函数应用链中,函数如何嵌套并不重要。
ReferentialFunctor<Long> leftSide = new ReferentialFunctor<>(x).map(f).map(g);
ReferentialFunctor<Long> rightSide = new ReferentialFunctor<>(x).map(f.andThen(g));
assert leftSide.equals(rightSide);
创建函子
类似于单子,我们将从创建参数化类型开始M 类型的值的包装T在里面。然而,与单子的情况不同,我们只需要实现一个方法:
- 地图负责执行操作。这里,我们传递一个函数,该函数在我们的上下文中对值进行操作,并返回已经包装在上下文中的另一个类型。此方法应该具有以下签名M (T -> R) .
在排除了数学背景之后函子以及它需要什么方法,我们可以开始实现它。
实现函子
import org.pasksoftware.functor.Functor;
import java.util.function.Function;
public class WrapperFunctor<T> implements Functor<T> {
private final T value;
public WrapperFunctor(T value) {
this.value = value;
}
@Override
public <R> WrapperFunctor<R> map(Function<T, R> f) {
return new WrapperFunctor<>(f.apply(value));
}
// For sake of asserting in Example
boolean valueEquals(T x) {
return value.equals(x);
}
}
我们准备好了函子实现,解释这20行代码中发生了什么。
上面到底发生了什么
我们实现的基础是带有不可变字段名为“价值“,负责存储我们的值。然后,我们有一个构造函数,它实际上使实例化我们的小函子实现成为可能。
接下来,我们有这个片段的核心部分地图方法,它将保证我们能够以函子法则的形式满足所需的条件。
描述完所有代码后,是时候看看函子的工作示例了。
import java.util.function.Function;
public class Example {
public static void main(String[] args) {
int x = 2;
Function<Integer, String> f = Object::toString;
// Task: mapping over the value inside the container object.
// Non-functor
Wrapper<Integer> wrapper = new Wrapper<>(x);
String mappedValue = f.apply(wrapper.value);
// One liner - Wrapper<String> mappedWrapper = new Wrapper<>(f.apply(x));
Wrapper<String> mappedWrapper = new Wrapper<>(mappedValue);
// Functor
WrapperFunctor<String> mappedWrapperFunctor = new WrapperFunctor<>(x).map(f);
assert mappedWrapperFunctor.valueEquals(mappedWrapper.value);
System.out.println("Values inside wrappers are equal");
}
}
在上面的代码片段中,你可以看到为什么函子除了它们有些简单的结构之外还是有用的。您会注意到,在不使用仿函数的情况下执行相同的操作需要在我们的API设计中进行一些更改,并且需要一些额外的样板文件以使其更具可读性。
总结
函子除了结构简单之外,还非常有用。它是范畴理论中最简单的常见容器,了解它的工作原理可以帮助你在旅途中走得更远。此外,基于函子的方法可以为任何类型容器内的值操作提供更具描述性和更清晰的解决方案。
关于仿函数的常见问题
什么是函子?
函子是范畴论中的一个概念,表示两个范畴之间的映射。
什么是函子定律?
每个函子实现必须满足两个法则:同一性和结合性。
什么情况下需要实现函子?
要实现仿函数,您需要一个参数化的类型M和方法映射。
最后
感谢阅读,点赞收藏+关注!资料已经给大家整理好了,更多的java课程学习路线,笔记,面试等架构资料,想要学习的同学可以点击获取资料