Haskell 中的存在类型和其他语言中的泛型

Ser*_*nin 6 haskell existential-type

我试图使用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
Run Code Online (Sandbox Code Playgroud)

我对“异构列表”有不同的概念,类似于Scala 中的 Shapeless。但在这里,它只是一个包含在仅添加类型约束的存在类型中的项目列表。它的元素的确切类型并没有体现在它的类型签名中,我们唯一知道的是它们都符合类型约束。

在面向对象的语言中,写这样的东西似乎很自然(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);
Run Code Online (Sandbox Code Playgroud)

我注意到存在类型仅可用作语言扩展,并且在大多数“基本”Haskell 书籍或教程中都没有描述它们,所以我的建议是这是一个非常高级的语言功能。

我的问题是,为什么?在具有泛型的语言中看起来很基本的东西(构造和使用其类型实现某些接口并以多态方式访问它们的对象列表),在 Haskell 中需要语言扩展、自定义语法并创建额外的包装器类型?有没有办法在不使用存在类型的情况下实现类似的东西,或者只是没有基本级别的用例?

或者也许我只是混淆了概念,存在类型和泛型意味着完全不同的东西。请帮助我理解它。

n. *_* m. 8

是的,存在类型和泛型意味着不同的东西。存在类型的使用类似于面向对象语言中的接口。您当然可以将一个放在列表中,但是使用接口不需要列表或任何其他泛型类型。有一个类型的变量Vocal来演示它的用法就足够了。

它在 Haskell 中没有被广泛使用,因为大多数时候它并不是真正需要的。

nonHeteroList :: [IO ()]
nonHeteroList = [print (), print 5, print True]
Run Code Online (Sandbox Code Playgroud)

在没有任何语言扩展的情况下做同样的事情。

存在类型(或面向对象语言中的接口)只不过是带有捆绑的方法字典的一段数据。如果您的字典中只有一种方法,请使用函数。如果您有多个,则可以使用元组或这些元组的记录。所以如果你有类似的东西

interface Shape {
   void Draw();
   double Area();
}
Run Code Online (Sandbox Code Playgroud)

你可以用 Haskell 来表达,例如,

type Shape = (IO (), Double)
Run Code Online (Sandbox Code Playgroud)

circle center radius = (drawCicrle center radius, pi * radius * radius)
rectangle topLeft bottomRight = (drawRectangle topLeft bottomRight, 
           abs $ (topLeft.x-bottomRight.x) * (topLeft.y-bottomRight.y))

shapes = [circe (P 2.0 3.5) 4.2, rectangle (P 3.3 7.2) (P -2.0 3.1)]
Run Code Online (Sandbox Code Playgroud)

虽然你可以用类型类、实例和存在来表达完全相同的东西

class Shape a where
  draw :: a -> IO ()
  area :: a -> Double

data ShapeBox = forall s. Shape s => SB s
instance Shape ShapeBox where
  draw (SB a) = draw a
  area (SB a) = area a

data Circle = Circle Point Double
instance Shape Circle where
  draw (Circle c r) = drawCircle c r
  area (Circle _ r) = pi * r * r

data Rectangle = Rectangle Point Point
instance Shape Rectangle where
  draw (Rectangle tl br) = drawRectangle tl br
  area (Rectangle tl br) = abs $ (tl.x - br.x) * (tl.y - br.y)

shapes = [Circle (P 2.0 3.5) 4.2, Rectangle (P 3.3 7.2) (P -2.0 3.1)]
Run Code Online (Sandbox Code Playgroud)

有了它,时间长了 N 倍。