为什么不能用Java声明Monad接口?

Stefan Dollase:

在开始阅读之前:这个问题不是关于理解monad的问题,而是关于确定Java类型系统的局限性的,这会限制Monad接口的声明


在我的努力去理解单子我读这个由埃里克利珀SO-答案上询问单子简单的解释问题。在那里,他还列出了可以在monad上执行的操作:

  1. 有一种方法可以将未放大类型的值转换为放大类型的值。
  2. 有一种方法可以将未放大类型的操作转换为遵循前面提到的功能组成规则的放大类型的操作
  3. 通常,有一种方法可以使未放大类型从放大类型中退回。(对于monad来说,这并非绝对必要,但通常存在这种操作。)

在阅读了有关monad的更多信息之后,我将第一个操作确定为return函数,将第二个操作确定为bind函数。我找不到第三次操作的常用名称,因此我将其称为unbox函数。

为了更好地理解monad,我继续尝试Monad用Java 声明一个通用接口。为此,我首先查看了上面三个功能的签名。对于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();
}

有两个缺陷:

  • bind函数应返回具体的实现,但是仅返回接口类型。这是一个问题,因为在具体的子类型上声明了取消装箱操作。我将其称为问题1
  • bind函数应检索一个函数作为参数。我们稍后会解决。

在接口声明中使用具体类型

这解决了问题1:如果我对monad的理解是正确的,那么该bind函数将始终返回与调用它的monad具有相同具体类型的新monad。因此,如果我有一个Monad名为接口的实现MM.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接口的另一种实现作为type参数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<>();
    

    但这显然不是我们想要的。

在自己的声明中使用带移位类型参数的类型

现在,让我们将function参数添加到bind函数中:如上所述,bind函数的签名如下所示:T1 -> M<T2>在Java中,这是type Function<T1, M<T2>>这是用参数声明接口的第一次尝试:

public interface Monad<T1, M extends Monad<?, ?>> {
    M bind(Function<T1, M> function);
}

我们必须将类型T1作为通用类型参数添加到接口声明中,以便可以在函数签名中使用它。第一个?T1返回的monad类型的M要用替换它T2,我们必须将T2自身添加为通用类型参数:

public interface Monad<T1, M extends Monad<T2, ?, ?>,
                       T2> {
    M bind(Function<T1, M> function);
}

现在,我们遇到了另一个问题。我们在Monad接口中添加了第三个类型参数,因此必须?在其用法上添加一个新参数我们将暂时不考虑新问题?,而要对第一时间进行调查?它是M返回的monad类型的M让我们尝试删除此?重命名MM1,并通过引入另一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

总结一下

我们确定了三个问题:

  1. 在抽象类型的声明中使用具体类型。
  2. 实例化一个将自身作为通用类型参数接收的类型。
  3. 声明一个类型,该类型在其声明中使用自己的类型参数。

问题是:Java类型系统缺少哪些功能?由于存在适用于monad的语言,因此这些语言必须以某种方式声明Monad类型。这些其他语言如何声明Monad类型?我找不到有关此的信息。我只找到有关声明单子像Maybe单子的信息。

我有想念吗?我可以使用Java类型系统正确解决这些问题之一吗?如果我不能用Java类型系统解决问题2,那么Java是否有理由不警告我关于不可实例化的类型声明?


如前所述,这个问题不是关于理解单子的。如果我对单子的理解是错误的,您可能会对此有所提示,但不要尝试给出解释。如果我对单子的理解是错误的,那么所描述的问题仍然存在。

这个问题也不是关于是否可以Monad用Java 声明接口。这个问题已经由Eric Lippert在上面链接的SO-answer中得到了答案:并非如此。这个问题是关于限制我执行此操作的限制到底是什么。埃里克·利珀特(Eric Lippert)将此称为高级类型,但我无法直视它们。

大多数OOP语言没有足够丰富的类型系统来直接表示monad模式本身。您需要一个类型系统,该系统支持比通用类型更高类型的类型。因此,我不会尝试这样做。相反,我将实现代表每个monad的泛型类型,并实现代表所需的三个操作的方法:将一个值转换为一个放大的值,将一个放大的值转换为一个值,以及将一个未放大的值转换为一个函数。放大值。

埃里克·利珀特(Eric Lippert):

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,并IFrobList<IBar>他们是所有类型。

从这时起,您就不再有关于长颈鹿是动物的子类的类的直觉了,等等。我们不需要。想想一个没有继承的世界。它不会再进入此讨论。

Java中的类是什么?好吧,最简单的方式来考虑一个类是,它是一组具有共同点的值名称,以便需要该类的实例时可以使用这些值中的任何一个。您有一个类Point,可以说,如果您有一个type变量Point,则可以为其分配任何实例PointPoint某种意义上讲类只是描述所有Point实例的集合的一种方式类比实例高

在Haskell中,还有通用和非通用类型。Haskell中的类不是一种类型。在Java中,类描述了一组任何时候需要类的实例时,都可以使用该类型的值。在Haskell中,一个类描述了一组类型这是Java类型系统缺少的关键功能。在Haskell中,类高于类型,而类型高于实例。Java只有两个层次结构;Haskell有三个。在Haskell中,您可以表达这样的想法:“只要我需要具有某些操作的类型,就可以使用该类的成员”。

(旁白:我想指出的是,我有点过分简化了。例如在Java List<int>Java中List<String>。这是两个“类型”,但是Java认为它们是一个“类”,因此从某种意义上讲Java也它的类比类型“高”,但是再说一次,您可以在Haskell中说list xlist 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必须具有两个操作:bindreturn这些操作的签名是什么?让我们return来看

  a -> m a

m a是Haskell的Java语言M<A>也就是说,这种手段m是一个通用型,a是一种类型,m am参数化用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>所需要的是该aM<A>,即bM<B>,并且有合适的SelectMany随后的单子图案。因此,这是在语言中嵌入“ monad识别器”而不直接在类型系统中表示的另一种方法。

(上一段实际上是一个过分简化的谎言;出于性能原因,此查询使用的绑定模式与标准monadic绑定略有不同。从概念上讲,这可以识别monad模式;实际上,细节略有不同。请在此处阅读有关详细信息:http: //ericlippert.com/2013/04/02/monads-part-twelve/(如果您有兴趣)。

还有几点要点:

我找不到第三项操作的常用名称,因此我将其称为unbox函数。

好的选择; 它通常称为“提取”操作。一个monad不必公开提取操作,但是当然bind需要某种方式才能A退出M<A>调用Function<A, M<B>>它,因此在逻辑上通常存在某种提取操作。

一个共鸣 -从某种意义上说是向后monad-需要extract公开操作;extract本质上是return倒退的。同样,comonad也需要进行extend某种bind向后的操作。有签名static M<B> Extend(M<A> m, Func<M<A>, B> f)

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Java

为什么我不能用链表::新?

来自分类Java

为什么不能在Java接口中定义静态方法?

来自分类Java

为什么我不能在接口中声明静态方法?

来自分类Java

为什么将Java接口方法声明为抽象方法?

来自分类Java

为什么在Java中使用接口名称声明变量?

来自分类Java

为什么必须用Java声明接口?

来自分类Java

为什么我不能用Java编写“实现AClass”?

来自分类Dev

为什么不能用指针接收器实现该接口

来自分类Java

为什么我的变量不能用Java解析?

来自分类Java

为什么我的变量不能用Java解析?

来自分类Dev

为什么不能用`= delete;`声明纯虚函数?

来自分类Dev

为什么编译器说:'enable_if'不能用于禁用此声明

来自分类Dev

为什么OptionT不能用于Try?

来自分类Dev

为什么泛型不能用代码编写的动作需要显式的参数声明

来自分类Dev

为什么不能用常量表达式声明数组?

来自分类Dev

为什么我不能用pygame射击?

来自分类Dev

为什么不能用if完成呢?

来自分类Dev

为什么不能用generator创建通用的bubbleSort?(Java)

来自分类Dev

为什么我们不能用默认值声明常量?

来自分类Dev

为什么不能用Java访问此创建的对象?

来自分类Dev

为什么我不能用方法添加?

来自分类Dev

ngInfiniteScroll为什么不能用于表格?

来自分类Dev

为什么 Spring DependsOn 注释不能用于自动连接接口

来自分类Dev

为什么 Java 泛型不能用于静态方法?

来自分类Dev

为什么FTP下载不能用Java正常工作

来自分类Dev

为什么 basename 不能用于变量?

来自分类Dev

为什么不能用xidel提取数据?

来自分类Dev

为什么 X 不能用作函数?

来自分类Dev

为什么我们不能用 auto 声明 std::function

Related 相关文章

热门标签

归档