【发布时间】:2021-05-21 02:22:51
【问题描述】:
我试图通过文章Haskell/Existentially quantified types 来掌握 Haskell 中存在类型的概念。乍一看,这个概念似乎很清晰,有点类似于面向对象语言中的泛型。主要的例子有一个叫做“异构列表”的东西,定义如下:
data ShowBox = forall s. Show s => SB s
heteroList :: [ShowBox]
heteroList = [SB (), SB 5, SB True]
instance Show ShowBox where
show (SB s) = show s
f :: [ShowBox] -> IO ()
f xs = mapM_ print xs
main = f heteroList
我对“异构列表”有不同的概念,例如Shapeless in Scala。但在这里,它只是一个包含在存在类型中的项目列表,它只添加了一个类型约束。它的元素的确切类型并没有体现在它的类型签名中,我们唯一知道的是它们都符合类型约束。
在面向对象的语言中,编写这样的东西似乎很自然(Java 中的示例)。这是一个普遍存在的用例,我不需要创建包装器类型来处理所有实现某个接口的对象列表。 animals 列表有一个泛型类型List<Vocal>,所以我可以假设它的元素都符合这个Vocal 接口:
interface Vocal {
void voice();
}
class Cat implements Vocal {
public void voice() {
System.out.println("meow");
}
}
class Dog implements Vocal {
public void voice() {
System.out.println("bark");
}
}
var animals = Arrays.asList(new Cat(), new Dog());
animals.forEach(Vocal::voice);
我注意到存在类型只能作为语言扩展使用,并且在大多数“基本”Haskell 书籍或教程中都没有描述它们,所以我的建议是这是一个相当高级的语言功能。
我的问题是,为什么?在具有泛型的语言中似乎基本的东西(构造和使用其类型实现某些接口并以多态方式访问它们的对象列表),在 Haskell 中需要语言扩展、自定义语法并创建额外的包装器类型?不使用存在类型就没有办法实现类似的目标,还是没有基本级别的用例?
或者我只是混淆了概念,存在类型和泛型意味着完全不同的东西。请帮助我理解它。
【问题讨论】:
-
请注意,像上面那样混合类型类和存在通常是antipattern,因为通常存在更简单的等效表示。
-
Haskell“扩展”经常被误解为“额外的东西”或“不是语言的一部分”,但事实并非如此。您不妨说泛型不是 C#“真正”的一部分,因为它们只出现在 C# 2.0 中。 Haskell 仅允许您控制正在使用的功能,仅此而已。
-
在大多数 OOP 语言中,几个特性被耦合在一起——命名空间、数据类型、存在多态性、子类型化、类型导向的动态调度、扩展/重用、封装……——并合并为一个抽象,类。 Haskell 拥有这些概念中的大部分,它只是将它们组织为单独的特性——函数、模块、类型类、各种扩展......——我们只使用我们需要的部分。
Language标志不一定是高级的,它们只是选择加入/明确的。我们通常使用存在性和 GADT 作为依赖类型的有限形式,以静态方式表示动态信息。