首页 > 解决方案 > 如何为 Haskell 实例赋予具体值?

问题描述

我正在尝试学习 Haskell,但我对这个臭名昭著的“刚性类型变量”错误感到有些困惑。

这是我的代码的相关部分:

class Ord v => Vector v where
  distance :: v -> v -> Double

-- with FlexibleInstances
instance Vector (Double, Double) where
  distance (a,b) (c,d) = -- function definition omitted

data KMeansState v = KMeansState { centroids :: [v] }

test :: [(Double,Double)]
test = [(0,0),(1,1)] :: [(Double,Double)]

initializeState :: Vector v => Int -> KMeansState v
initializeState n = KMeansState test

编译器给我的具体投诉:

• Couldn't match type ‘v’ with ‘(Double, Double)’
      ‘v’ is a rigid type variable bound by
        the type signature for:
          initializeState :: forall v.
                             Vector v =>
                             Int -> Double -> KMeansState v
        at Chapter06.hs:32:1-61
      Expected type: KMeansState v
        Actual type: KMeansState (Double, Double)

我的理解是编译器说我的initializeState函数需要能够返回(或 Haskell 中的任何正确词),KMeansState其类型参数是任何类型的实例Vector,而我的函数的实际实现只会返回一种类型,即一个KMeansState (Double, Double)

我不清楚的是如何从这个initializeState函数中得到一个“具体”的值。在程序中的某个时刻,我需要给KMeansState构造函数一个实际值,该值将具有实际类型,并且编译器似乎在说我不能这样做。

如果我将test函数的值硬编码到initializeState. 也就是说,这成功地进行了类型检查,即使传递给KMeansState函数的值在功能上应该是相同的。

class Ord v => Vector v where
  distance :: v -> v -> Double

-- with FlexibleInstances
instance Vector (Double, Double) where
  distance (a,b) (c,d) = -- function definition omitted

data KMeansState v = KMeansState { centroids :: [v] }

-- test :: [(Double,Double)]
-- test = [(0,0),(1,1)] :: [(Double,Double)]

initializeState :: Vector v => Int -> KMeansState v
initializeState n = KMeansState [(0,0),(1,1)]::[(Double,Double)]

有人可以澄清这里发生了什么以及我如何编译它吗?谢谢!

标签: haskell

解决方案


这是对 Haskell 类型类是什么/它们如何工作的常见的基本误解。具体来说,Haskell 类不像OO 语言中的类。它在某些方面就像一个接口/抽象类,但也不是真的。

特别是,您不能拥有“类的具体值”。类没有值,它们有实例——但那些是类型,而不是值。(这些类型可能有也可能没有值——事实上,严格来说,它们根本不需要是类型,只是类型级别的实体。)

打算用你对initializeState函数的签名说的是“结果是一个类型的值KMeansState v,其中v某个类型是Vector类的一个实例,但我不想告诉你是哪个”。它实际上说的是,“对于v你选择的任何类型,只要它是Vector类的一个实例,它就是一个initializeState产生KMeansState v值的函数”。由于“for any”,又名“forall”或∀,我们称其为普遍量化类型

同时,预期的类型是存在量化的。为什么会这样称呼它有点奇怪;如果我将其表述为“结果是一个值r,其中存在v具有Vector实例r的类型,例如具有类型KMeansState v”,这更容易理解。

虽然 Haskell 一直都有普遍量化的类型,但它并没有真正存在的类型。您可以做的是将存在包装在自定义类型中:

{-# LANGUAGE GADTs #-}

data SomeVectorMeanState where
  SomeVectorMeanState :: Vector v => KMeansState v -> SomeVectorMeanState

initializeState :: Int -> SomeVectorMeanState
initializeState n = SomeVectorMeanState $ KMeansState test

但这并没有真正完成任何有用的事情。事实上,您将无法以任何方式使用向量值,因为具体类型是未知的。见https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/

相反,正如 Willem Van Onsem 评论的那样,您可能应该只使用

initializeState :: Int -> KMeansState (Double,Double)

或者,如果您不想让向量类型如此明显,则(Double,Double)可以将其包装在 a typeornewtype中。


推荐阅读