Java类型系统缺少什么功能?这些其他语言如何声明Monad类型?
好问题!
埃里克·利珀特(Eric Lippert)将其称为高级类型,但我无法直视它们。
你不是一个人。但是他们实际上并不像听起来那样疯狂。
让我们通过查看Haskell如何声明monad为“类型”来回答您的两个问题-
您将在一分钟内看到为什么引用。我做了一些简化。标准monad模式在Haskell中还有其他几个操作:
class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a
男孩,看起来既简单又完全不透明,不是吗?
在这里,让我简化一下。Haskell让您声明自己的infix运算符进行绑定,但是我们将其称为bind:
class Monad m where bind :: m a -> (a -> m b) -> m b return :: a -> m a
好吧,至少现在我们可以看到其中有两个monad操作。其余的是什么意思?
如您所知,首先要注意的是“更高种类的类型”。(正如Brian指出的那样,我在最初的回答中稍微简化了这个行话。您的问题吸引了Brian的注意也很有趣!)
在Java中,“类”是 一种
“类型”,并且类可以是通用的。因此,在Java中,我们已经有了
int,并
IFrob和
List<IBar>他们是所有类型。
从这时起,您就不再有关于长颈鹿是动物的子类的类的直觉了,等等。我们不需要。想想一个没有继承的世界;它不会再进入此讨论。
Java中的类是什么?好吧,最简单的方式来考虑一个类是,它是一 组具有共同点的值 的 名称, 以便 在
需要该类的实例时可以使用这些值中的任何一个。您有一个类
Point,可以说,如果您有一个type变量
Point,则可以为其分配任何实例
Point。从
Point某种意义上讲,类只是描述
所有
Point实例的集合的_一种方式。类比 _实例高 。
在Haskell中,还有通用类型和非通用类型。Haskell中的类 不是 一种类型。在Java中,类描述了一组 值
;任何时候需要该类的实例时,都可以使用该类型的值。在Haskell中,一个类描述了一组 类型 。这是Java类型系统缺少的关键功能。在Haskell中
,类高于类型,而类型高于实例。
Java只有两个层次结构;Haskell有三个。在Haskell中,您可以表达这样的想法:“只要我需要具有某些操作的类型,就可以使用该类的成员”。
(旁白:我想指出的是,我有点过分简化了。例如
List<int>,以Java
和Java为例
List<String>。这是两个“类型”,但Java认为它们是一个“类”,因此从某种意义上讲Java也它的类比类型“高”,但是再说一遍,您可以在Haskell中说
listx和
list y类型,
list这是比类型高的东西;它是可以产生类型的东西。实际上,说Java有 3个 级别,而Haskell有 4 个
级别,则更准确地说,不过,重点仍然是:Haskell的概念是描述比Java更强大的类型上可用的操作。这在下面有更详细的说明。)
那么这与接口有何不同?这听起来像Java中的接口-您需要一种具有某些操作的类型,然后定义一个描述这些操作的接口。我们将看到Java接口缺少的内容。
现在我们可以开始理解这个Haskell了:
class Monad m where
那么,什么是
Monad?这是一堂课。什么是课程?它是一组具有一些共同点的类型,因此只要您需要具有某些操作的类型,就可以使用
Monad类型。
假设我们有一个属于该类的类型;称呼它
m。为了使该类型成为类的成员,必须对该类型进行哪些操作
Monad?
bind :: m a -> (a -> m b) -> m b return :: a -> m a
操作的名称位于的左侧,
::签名位于右侧。因此
Monad,类型
m必须具有两个操作:
bind和
return。这些操作的签名是什么?让我们
return先来看。
a -> m a
m a是Haskell的Java语言
M<A>。也就是说,这种手段
m是一个通用型,
a是一种类型,
m a是
m参数化用
a。
x -> yHaskell中的语法是“具有类型
x并返回类型的函数
y”。是
Function<X, Y>。
放在一起,我们就有
return了一个函数,该函数接受type的参数
a并返回type 的值
m a。或用Java
static <A> M<A> Return(A a);
bind有点难。我认为OP非常了解此签名,但是对于不熟悉简洁的Haskell语法的读者,让我对其进行扩展。
在Haskell中,函数仅接受一个参数。如果要使用两个参数的函数,则可以创建一个接受一个参数并返回 另一个具有一个参数的函数的函数 。所以如果你有
a -> b -> c
那你有什么 接受
a并返回的函数
b->c。因此,假设您想创建一个接受两个数字并返回其总和的函数。您将创建一个使用第一个数字的函数,并返回一个使用第二个数字并将其添加到第一个数字的函数。
在Java中,您会说
static <A, B, C> Function<B, C> F(A a)
因此,如果您想要C,并且拥有A和B,则可以说
F(a)(b)
合理?
好吧
bind :: m a -> (a -> m b) -> m b
实际上是一个需要两件事的函数:an
m a和a
a -> m b并返回an
m b。或者,在Java中,它直接是:
static <A, B> Function<Function<A, M<B>>, M<B>> Bind(M<A>)
或者,更惯用Java:
static <A, B> M<B> Bind(M<A>, Function<A, M<B>>)
现在,您了解了Java为什么不能直接表示monad类型的原因。它没有能力说“我有一类具有相同模式的类型”。
现在,您可以在Java中创建所需的所有monadic类型。您不能做的是创建一个表示“此类型为monad类型”想法的接口。您需要做的是:
typeinterface Monad<M>{ static <A> M<A> Return(A a); static <A, B> M<B> Bind(M<A> m, Function<A, M<B>> f);}看到类型接口如何谈论泛型类型本身吗?一元类型是
M具有一个类型参数 且 具有这两种 静态
方法的任何类型。但是您不能在Java或C#类型的系统中执行此操作。
Bind当然可以是采用
M<A>as
的实例方法
this。但是
Return除了静态之外,别无他法。Java无法让您(1)通过未 构造的
泛型类型对接口进行参数化,并且(2)无法指定静态成员是接口协定的一部分。
由于存在适用于monad的语言,因此这些语言必须以某种方式声明Monad类型。
好吧,您会这样想,但实际上却没有。首先,当然,任何具有足够类型系统的语言都可以定义单子类型。您可以在C#或Java中定义所需的所有monadic类型,只是不能说出它们在类型系统中的共同点。例如,您不能创建只能通过monadic类型进行参数化的泛型类。
其次,您可以通过其他方式在语言中嵌入monad模式。C#无法说“此类型与monad模式匹配”,但是C#具有内置于该语言中的查询理解(LINQ)。查询理解适用于任何单子类型!只是必须调用bind操作
SelectMany,这有点奇怪。但是,如果您看一下的签名
SelectMany,就会发现它只是
bind:
static IEnumerable<R> SelectMany<S, R>( IEnumerable<S> source, Func<S, IEnumerable<R>> selector)
这是
SelectMany序列monad 的实现
IEnumerable<T>,但如果您编写的是C#
from x in a from y in b select z
然后
a的类型可以是 任何
单子类型,而不仅仅是
IEnumerable<T>。所需要的是该
a是
M<A>,即
b是
M<B>,并且有合适的
SelectMany随后的单子图案。因此,这是在语言中嵌入“ monad识别器”而不直接在类型系统中表示的另一种方法。
(上一段实际上是一个过分简化的谎言;出于性能原因,此查询使用的绑定模式与标准monadic绑定略有不同。 从概念上讲,
这可以识别monad模式;实际上,细节略有不同。请在此处阅读有关详细信息:http:
//ericlippert.com/2013/04/02/monads-part-
twelve/(如果您有兴趣)。
还有几点要点:
我找不到第三项操作的常用名称,因此我将其称为unbox函数。
好的选择; 它通常称为“提取”操作。一个 monad
不必公开提取操作,但是当然
bind需要某种方式才能
A退出
M<A>调用
Function<A, M<B>>它,因此在逻辑上通常存在某种提取操作。
一个 共鸣 -从某种意义上说是向后的单子-
要求
extract公开操作;
extract本质上是
return倒退的。同样,comonad也需要进行
extend某种
bind后退的操作。有签名
staticM<B> Extend(M<A> m, Func<M<A>, B> f)



