为什么 Monad 接口不能用 Java 声明?在接口声明中使用具体类型在类型自己的声明中使用带有移位类型参数的类型总结一下
在你开始阅读之前:这个问题不是关于理解monads,而是关于识别Java类型系统的局限性,它阻止了接口的声明。Monad
在我努力理解monads的过程中,我读了Eric Lippert的这个SO-answer,其中提出了一个关于monad的简单解释的问题。在那里,他还列出了可以在monad上执行的操作:
- 有一种方法可以获取未放大类型的值并将其转换为放大类型的值。
- 有一种方法可以将未放大类型的操作转换为对放大类型的操作,该操作遵循前面提到的功能组合规则
- 通常有一种方法可以将未放大的类型从放大类型中取出。(最后一点对于monad来说并不是绝对必要的,但经常存在这样的操作。
在阅读了有关monads的更多信息后,我将第一个操作确定为函数,将第二个操作确定为函数。我无法找到第三个操作的常用名称,因此我将仅将其称为函数。return
bind
unbox
为了更好地理解monads,我继续尝试在Java中声明一个通用接口。为此,我首先查看了上述三个函数的签名。对于Monad,它看起来像这样:Monad
M
return :: T1 -> M<T1>
bind :: M<T1> -> (T1 -> M<T2>) -> M<T2>
unbox :: M<T1> -> T1
该函数不在 的实例上执行,因此它不属于接口。相反,它将作为构造函数或工厂方法实现。return
M
Monad
同样在目前,我从接口声明中省略了该函数,因为它不是必需的。对于接口的不同实现,此函数将有不同的实现。unbox
因此,接口仅包含函数。Monad
bind
让我们尝试声明接口:
public interface Monad {
Monad bind();
}
有两个缺陷:
- 该函数应返回具体实现,但它仅返回接口类型。这是一个问题,因为我们在具体的子类型上声明了取消装箱操作。我将这称为问题1。
bind
- 该函数应将函数作为参数检索。我们稍后将对此进行讨论。
bind
在接口声明中使用具体类型
这解决了问题1:如果我对monad的理解是正确的,那么该函数总是返回一个与它被调用的monad相同的具体类型的新monad。所以,如果我有一个名为 的接口的实现,那么将返回另一个但不是.我可以使用泛型来实现这一点:bind
Monad
M
M.bind
M
Monad
public interface Monad<M extends Monad<M>> {
M bind();
}
public class MonadImpl<M extends MonadImpl<M>> implements Monad<M> {
@Override
public M bind() { /* do stuff and return an instance of M */ }
}
起初,这似乎有效,但是这至少有两个缺陷:
-
一旦一个实现类没有提供自身,而是提供接口的另一个实现作为类型参数,就会崩溃,因为这样方法将返回错误的类型。例如
Monad
M
bind
public class FaultyMonad<M extends MonadImpl<M>> implements Monad<M> { ... }
将返回一个实例,其中应返回 的实例。但是,我们可以在文档中指定此限制,并将此类实现视为程序员错误。
MonadImpl
FaultyMonad
-
第二个缺陷更难解决。我将它称为问题2:当我尝试实例化类时,我需要提供的类型。让我们试试这个:
MonadImpl
M
new MonadImpl<MonadImpl<MonadImpl<MonadImpl<MonadImpl< ... >>>>>()
要获得有效的类型声明,这必须无限持续下去。这是另一种尝试:
public static <M extends MonadImpl<M>> MonadImpl<M> create() { return new MonadImpl<M>(); }
虽然这似乎有效,但我们只是将问题推迟到被调用者。以下是该函数对我有用的唯一用法:
public void createAndUseMonad() { MonadImpl<?> monad = create(); // use monad }
这基本上可以归结为
MonadImpl<?> monad = new MonadImpl<>();
但这显然不是我们想要的。
在类型自己的声明中使用带有移位类型参数的类型
现在,让我们将函数参数添加到函数中:如上所述,函数的签名如下所示:。在Java中,这是类型。下面是第一次尝试使用该参数声明接口:bind
bind
T1 -> M<T2>
Function<T1, M<T2>>
public interface Monad<T1, M extends Monad<?, ?>> {
M bind(Function<T1, M> function);
}
我们必须将类型作为泛型类型参数添加到接口声明中,以便我们可以在函数签名中使用它。第一个是 类型 返回的 monad 。要将其替换为 ,我们必须将自身添加为泛型类型参数:T1
?
T1
M
T2
T2
public interface Monad<T1, M extends Monad<T2, ?, ?>,
T2> {
M bind(Function<T1, M> function);
}
现在,我们遇到了另一个问题。我们在接口中添加了第三个类型参数,因此我们必须在它的用法中添加一个新的。我们将暂时忽略新的,以首先调查现在。它是类型 返回的 monad 。让我们尝试通过重命名和引入另一个来删除它:Monad
?
?
?
M
M
?
M
M1
M2
public interface Monad<T1, M1 extends Monad<T2, M2, ?, ?>,
T2, M2 extends Monad< ?, ?, ?, ?>> {
M1 bind(Function<T1, M1> function);
}
在以下位置引入其他结果:T3
public interface Monad<T1, M1 extends Monad<T2, M2, T3, ?, ?>,
T2, M2 extends Monad<T3, ?, ?, ?, ?>,
T3> {
M1 bind(Function<T1, M1> function);
}
并介绍另一个结果:M3
public interface Monad<T1, M1 extends Monad<T2, M2, T3, M3, ?, ?>,
T2, M2 extends Monad<T3, M3, ?, ?, ?, ?>,
T3, M3 extends Monad< ?, ?, ?, ?, ?, ?>> {
M1 bind(Function<T1, M1> function);
}
我们看到,如果我们试图解决所有问题,这种情况将永远持续下去。这是问题 3。?
总结一下
我们发现了三个问题:
- 在抽象类型的声明中使用具体类型。
- 实例化将自身作为泛型类型参数接收的类型。
- 声明一个类型,该类型在其声明中使用自身,并带有移位的类型参数。
问题是:Java类型系统中缺少哪些功能?由于有些语言与monads一起工作,因此这些语言必须以某种方式声明类型。这些其他语言如何声明类型?我无法找到有关此内容的信息。我只找到有关具体monads声明的信息,比如monad。Monad
Monad
Maybe
我错过了什么吗?我能否正确解决 Java 类型系统的这些问题之一?如果我无法解决Java类型系统的问题2,那么Java为什么不警告我不可实例化的类型声明吗?
如前所述,这个问题不是关于理解monads。如果我对monads的理解是错误的,你可以给出一个提示,但不要试图给出解释。如果我对monads的理解是错误的,那么所描述的问题仍然存在。
这个问题也不是关于是否可以在Java中声明接口。埃里克·利珀特(Eric Lippert)在上面链接的SO-answer中已经回答了这个问题:事实并非如此。这个问题是关于阻止我这样做的限制究竟是什么。埃里克·利珀特(Eric Lippert)将此称为高级类型,但我无法理解它们。Monad
大多数OOP语言没有足够丰富的类型系统来直接表示monad模式本身;您需要一个支持类型高于泛型类型的类型类型的类型系统。所以我不会试图这样做。相反,我将实现表示每个 monad 的泛型类型,并实现表示您需要的三个操作的方法:将值转换为放大值,将放大值转换为值,并将未放大值上的函数转换为放大值上的函数。