OOP接口和FPtypes之间的区别

可能重复:
Java的接口和Haskell的types类:差异和相似之处?

当我开始学习Haskell时,我被告知types类比接口更强大/不同。

一年以后,我已经广泛地使用了接口和types类,我还没有看到一个例子或解释,他们是如何不同。 这不是一个自然而然的启示,我错过了一些显而易见的事实,或者实际上并没有真正的区别。

search互联网并没有带来任何实质性的进展。 所以呢,你有答案吗?

你可以从多个angular度来看这个。 其他人会不同意,但是我认为OOP接口是开始理解types类的好地方(当然,相比之下从无到有)。

人们喜欢从概念上指出,types类对types进行分类很像集合 – “支持这些操作的types集合,以及不能在语言本身中编码的其他期望”。 这是有道理的,偶尔也会声明一个没有方法的types类,并说“只有当你的types满足一定的要求时,才能使你的types成为这个类的一个实例”。 OOP接口很less发生这种情况。

就具体的差异而言,types类比OOP接口更强大的方法有多种:

  • 最大的一点是types类将types实现接口的声明和types声明分开。 使用OOP接口,可以列出types在定义types时所实现的接口,并且以后无法添加更多接口。 对于types类,如果您创build了一个给定types“up the module hierarchy”可以实现但不知道的新types类,则可以编写一个实例声明。 如果你有不同的第三方types和types的类,你可以写一个实例声明。 在OOP接口的类似情况下,大多数情况下,只是OOP语言已经发展出“devise模式”(适配器)来解决这个限制。

  • 下一个最大的(当然是主观的)是,在概念上,OOP接口是一堆可以在实现接口的对象上调用的方法,types类是一组方法,它们可以用于成员types的类。 区别很重要。 由于type类方法是通过引用types来定义的,而不是对象,所以将types的多个对象作为参数(相等和比较运算符)或返回types的对象作为结果没有任何障碍各种算术运算),甚至types的常量(最小和最大边界)。 OOP接口不能做到这一点,而OOP语言已经进化了devise模式(如虚拟克隆方法)来解决这个限制。

  • OOP接口只能定义types; types类也可以被定义为所谓的“types构造函数”。 在各种C派生的OOP语言中使用模板和generics定义的各种集合types是types构造函数:List将typesT作为参数并构造typesList <T>。 使用Type类可以为types构造函数声明接口:say,对集合types的映射操作,在集合的每个元素上调用提供的函数,并将结果收集到集合的新副本中 – 可能使用不同的元素types! 同样,你不能用OOP接口来做到这一点。

  • 如果一个给定的参数需要实现多个接口,那么使用types类就可以轻而易举地列出它应该是哪个接口的成员; 使用OOP接口,只能将一个接口指定为给定指针或引用的types。 如果你需要它来实现更多,你唯一的select是没有吸引力的,例如在签名中写入一个接口并将其转换为其他接口,或为每个接口添加单独的参数,并要求它们指向相同的对象。 你甚至不能通过声明一个新的,空的接口inheritance你所需要的接口来解决它,因为一个types不会自动被认为是实现你的新接口,因为它实现了它的祖先。 (如果你可以在事实之后声明实现,这不会是这样的问题,但是,你也不能这么做。)

  • 对上面的一种反向情况sorting,可以要求两个参数具有实现特定接口的types, 并且它们是相同的types。 使用OOP接口只能指定第一部分。

  • types类的实例声明更加灵活。 使用OOP接口,只能说“我声明了一个typesX,它实现了接口Y”,其中X和Y是特定的。 对于types类,可以说“元素types满足这些条件的所有列表types都是Y的成员”。 (你也可以说“X和Y的所有成员都是Z的成员”,尽pipe在Haskell中这是有问题的。

  • 所谓的“超类约束”比单纯的接口inheritance更为灵活。 使用OOP接口,你只能说“对于一个types来实现这个接口,它也必须实现这些其他接口”。 这也是types类最常见的情况,但是超类的约束也让你说“SomeTypeConstructor <ThisType>必须实现某某接口”,或者“应用于该types的这个types函数的结果必须满足所谓的”如此约束“等等。

  • 这是Haskell中的一个语言扩展(与types函数一样),但是可以声明涉及多个types的types类。 例如,一个同构类:可以无损地从一个转换到另一个的types对。 再次,不可能与OOP接口。

  • 我相信还有更多。

值得注意的是,在添加generics的OOP语言中,这些限制中的一部分可以被删除(第四,第五第二点)。

另一方面,OOP接口可以做的事情有两个重要的事情,而本地的types类不能:

  • 运行时dynamic分派。 在OOP语言中,传递和存储指向实现接口的对象的指针是微不足道的,并且在运行时调用它的方法,这将根据对象的dynamic运行时types来解决。 相比之下,types约束默认情况下都是在编译时确定的 – 也许令人惊讶的是,在绝大多数情况下,这是您所需要的。 如果您确实需要dynamic分派,那么您可以使用所谓的存在types(目前是Haskell中的一种语言扩展):一个构造,它忘记了对象的types,只记得(根据您的select)它遵守某些types的类别约束。 从这一点来看,它的行为基本上和用OOP语言实现接口的对象的指针或引用完全相同,types类在这个区域没有不足。 (应该指出的是,如果你有两个实现相同types的存在和一个需要两个types参数的types方法,你不能使用存在作为参数,因为你不知道是否现存的是同一types的,但是与OOP语言相比,首先不能有这样的方法,这不算什么损失)。

  • 运行时将对象转换为接口。 在OOP语言中,可以在运行时使用指针或引用,并testing它是否实现了一个接口,如果是,则将其“转换”到该接口。 types类本身没有任何等价的东西(这在某些方面是一个优点,因为它保留了一个名为'parametricity'的属性,但我不会在这里进入)。 当然,没有什么能阻止你添加一个新的types类(或增加一个现有的类)和方法来把types的对象转换成你想要的任何types类的存在。 (你也可以像图书馆一样实现这个function,但是这个function要多一些,我打算把它完成并上传到Hackage,我保证!)

(我要指出的是,虽然你可以做这些事情,但是很多人认为模仿OOP这种不好的风格,并build​​议你使用更简单的解决scheme,比如显式的函数logging,而不是types类,具有完整的一streamfunction,这个选项也不弱。)

在操作上,OOP接口通常通过在对象本身中存储一个或多个指针来实现,这些指针指向对象实现的接口的函数指针表。 types类通常通过“字典传递”来实现(对于像Haskell一样通过装箱进行多态的语言,而不是像C ++那样通过多实例进行多态):编译器隐式地将指针传递给函数表(以及常量)作为每个使用typestypes的函数的隐藏参数,无论涉及多less个对象,函数都会获得一个副本(这就是为什么你要做上面第二点提到的东西)。 存在types的实现看起来很像OOP语言所做的事情:指向types类字典的指针与对象一起存储为“证据”,“被遗忘”types是其成员。

如果你曾经阅读过有关C ++的“概念”提案(就像最初为C ++ 11提出的那样),那么基本上Haskell的types类就是为C ++的模板重新构想的。 我有时会认为有一种简单地使用C ++ – with-concepts的语言,将一半的面向对象和虚拟函数撕掉,清理语法和其他的瑕疵,并在需要运行时添加存在types基于types的dynamic调度。 (更新: Rust基本上是这样的,还有很多其他的好东西。)

我假设你正在谈论Haskelltypes的类。 这不是真正的接口和types的区别。 正如名称所述,一个types类只是一个具有一组通用函数的types(和相关types,如果启用了TypeFamilies扩展)。

但是,Haskell的types系统本身比C#的types系统更强大。 这允许你在Haskell中编写types类,而你不能用C#expression。 即使像Functor一样简单的types也不能用C#表示:

 class Functor f where fmap :: (a -> b) -> fa -> fb 

C#的问题是generics不能通用。 换句话说,在C#中,只有types*可以是多态的。 Haskell允许多态types的构造函数,所以任何types的types都可以是多态的。

这就是为什么Haskell中的许多强大的generics函数( mapMliftA2等等)不能用大多数语言来expression的,而不是一个强大的types系统。

主要区别 – 使types类比接口更灵活 – 是types类与数据types无关,可以在之后添加。 另一个区别(至less对于Java来说)是可以提供默认的实现。 一个例子:

 //Java public interface HasSize { public int size(); public boolean isEmpty(); } 

有这个接口是好的,但没有办法将其添加到现有的类而不改变它。 如果幸运的话,这个类是非最终的(比如说ArrayList ),所以你可以写一个实现它的接口的子类。 如果class级是最后的(比如说String ),那你倒霉了。

把这个和Haskell比较一下。 你可以编写types:

 --Haskell class HasSize a where size :: a -> Int isEmpty :: a -> Bool isEmpty x = size x == 0 

而且您可以将现有的数据types添加到该类中而不用触摸它们:

 instance HasSize [a] where size = length 

types类的另一个不错的属性是隐式调用。 例如,如果你有一个Java Comparator ,你需要把它作为显式值传入。 在Haskell中,只要适当的实例在范围内,就可以自动使用等价的Ord