Java中的ADT类多态(不改变类)

Haskell中,我可以定义以下数据类型:

data Tree = Empty | Leaf Int | Node Tree Tree 

然后写这样的多态函数:

 depth :: Tree -> Int depth Empty = 0 depth (Leaf n) = 1 depth (Node lr) = 1 + max (depth l) (depth r) 

Java中,我可以使用接口模拟代数数据类型:

 interface Tree {} class Empty implements Tree {} class Leaf implements Tree { int n; } class Node implements Tree { Tree l; Tree r; } 

但是,如果我尝试使用类似Haskell的多态,我会收到一个错误:

 int depth(Empty node) { return 0; } int depth(Leaf node) { return 1; } int depth(Node node) { return 1 + Math.max(depth(node.l), depth(node.r)); // ERROR: Cannot resolve method 'depth(Tree)' } 

克服这个问题的正确方法是将方法 depth() 放到每个类中 。 但是,如果我不想把它放在那里怎么办? 例如,方法depth()可能与Tree没有直接关系,将它添加到class会破坏业务逻辑。 或者,更糟糕的是, Tree可能写在我无法访问的第三方库中。 在这种情况下,实现类似ADT的多态性最简单方法是什么?

为了以防万一,目前我正在使用以下语法,这显然是不受欢迎的:

 int depth(Tree tree) { if (tree instanceof Empty) depth((Empty)tree) if (tree instanceof Leaf) depth((Leaf)tree); if (tree instanceof Node) depth((Node)tree); else throw new RuntimeException("Don't know how to find depth of " + tree.getClass()); } 

尝试这样的事情。

对不起,我的Java非常生疏。 如果,与我不同,您可以记住语法,您可以使用Javagenerics来将ObjectInteger或您正在编写的方法所需的任何类。 但你不能(你能吗?)返回原始类型,抱歉。

 interface TreeFolder { Object onEmpty(); Object onLeaf (int n); Object onNode (Tree l, Tree r); } interface Tree { Object fold (TreeFolder f); } class Empty implements Tree { Object fold (TreeFolder f) { return f.onEmpty(); } } class Leaf implements Tree { private int n; Object fold (TreeFolder f) { return f.onLeaf (n); } } class Node implements Tree { private Tree l, r; Object fold (TreeFolder f) { return f.onNode (l, r); } } // meanwhile, in a class in another package far far away... Object depth (Tree tree) { return tree.fold (new TreeFolder() { Object onEmpty() { return new Integer(0); } Object onLeaf (int n) { return new Integer(n); } Object onNode (Tree l, Tree r) { Integer ll = (Integer) l.fold (this); Integer rr = (Integer) r.fold (this); return new Integer (ll.intValue() + rr.intValue()); } }); } 

请注意,在depth()我必须在Tree参数上手动递归(调用fold() )。 您可以选择在Node.fold()预先递归它们(并相应地更改TreeFolder ),但是您必须递归—如果您愿意,您不能选择仅递归到左子树。 (在Haskell中,由于懒惰,我们不必进行权衡。)

这是一个粗略的草图,你可以通过一般和可扩展的方式来解决这个问题。 它不会直接在所有情况下都有效,但可能会帮助您入门。

首先,一些开始的假设:

  1. 我们不希望任何特定于depth东西添加到Tree类中。
  2. 我们不想失去静态类型的好处。

关键是要意识到你想在这里重新创建的Haskell代码不是Tree类型本身,而是它上面的模式匹配 。 因此,我们首先将“树上的模式匹配”本身作为第一类(ha,ha)实体。 使用C#-ish伪代码,因为我多年没有使用过Java:

 interface MatchTree { R matchEmpty(Empty empty); R matchLeaf(Leaf leaf); R matchNode(Node node); } 

要使用此reified模式匹配,我们需要在Tree上使用适当的方法:

 interface Tree { R patternMatch(MatchTree patterns); } 

然后,每个单独的Tree子类型都可以通过调用适当的MatchTree方法并将其自身作为参数来实现该函数。

等效的Haskell将是这样的:

 data MatchTree r = MatchTree { matchEmpty :: r , matchLeaf :: Int -> r , matchNode :: Tree -> Tree -> r } 

…可以很容易地看到与案例表达直接对应:

 match tree z fl fn = case tree of Empty -> z Leaf x -> fl i Node lt rt -> fn lt rt 

这种风格的模式匹配在OOP圈子中被称为“访客模式”,顺便说一句。

例如,方法depth()可能与Tree没有直接关系,将它添加到class会破坏业务逻辑。 或者,更糟糕的是,Tree可能写在我无法访问的第三方库中。 在这种情况下,实现类似ADT的多态性的最简单方法是什么?

在这种情况下 – 我建议你使用设计模式访问者 。 它允许您分离数据表示和处理逻辑 ,甚至更多 – 它允许您实现不同的处理策略。

@stemm是正确的,访问者模式非常适合这个问题。 不过,我还建议您查看众所周知的访客模式的修改版本。 一位博主发明了这种教会编码模式 。 这种模式更加密集,并且具有比访问者模式更多的function风格。

编辑:这是你不想要的答案depth()Tree界面中放置depth() ),但我认为它无论如何都值得进行全面分析。


更广泛地说,这是使用类实现sum类型的问题。 在面向对象的语言中有一种非常常见的方法来使用和类型。 即, 解释器模式

 interface Tree { int depth(); } class Empty implements Tree { int depth(){ return 0; } class Leaf implements Tree { int n; int depth(){ return 1; } } class Node implements Tree { Tree l; Tree r; int depth(){ return max(depth(l), depth(r)); } } 

让我们将它与haskell方法进行比较! 很明显,类的作者可以有任意多种类型( EmptyLeafNode )和方法( depth()numLeafs() )。 但是,想要扩展此树库的外部代码呢?

使用haskell中的代数数据类型,外部代码库可以添加类型:: Tree -> a树函数,如果库公开Tree(..) (类型本身和所有三个构造函数)。 但是,无法向Tree添加新的构造函数,如下所示:

 -- Code far far away can't do this in haskell data Tree = ... | ... | Node3 Tree Tree Tree 

但是在java中使用解释器模式时,却恰恰相反。 无法向Tree接口添加新方法,但可以添加一个新的构造函数,如下所示:

 -- Code far far away *can* do this in java class Node3 implements Tree { Tree l; Tree mid; Tree r; int depth(){ ... } } 

总之,如果符合以下条件,此设计模式非常有

  • 其他人希望在代数数据结构中添加术语。
  • 你希望全类型安全

但它有点令人不满意,因为:

  • 其他人不能添加像numNodes()这样的reducer函数
  • 人们觉得在每个class级都必须有一个关于树木的方法。 我们更喜欢每种方法对树进行一次匹配模式,而不是我们以一种转置方式进行。

我没有找到任何令人愉快的解决方案

我希望这可以帮到你。

 interface Tree { } class Empty implements Tree { } class Leaf implements Tree { int n; } class Node implements Tree { Tree l; Tree r; } class Test{ public static void main (String args[]){ Test p = new Test(); Empty e = new Empty(); System.out.println(p.depth(e)); Leaf t = new Leaf(); System.out.println(p.depth(t)); Node n = new Node(); nl = t; nr = e; System.out.println(p.depth(n)); } int depth(Tree tree) { if(tree instanceof Leaf){ return 1; } return 0; } int depth(Node node) { return 1 + Math.max(depth(node.l), depth(node.r)); } } } 

祝你好运!