为什么GHC没有“向上”将矢量变为可折叠?

时间:2017-10-23 14:35:19

标签: haskell typeclass

为了学习如何编写更通用的代码,我试着写 一个简单的Array类。我们的想法是只编写一些简单的数组操作 使用Array类提供的函数,然后将不同的数据类型写为实例。 所以我想写出不同的数组表示。

对于第一次尝试,数组具有多个行和列以及可折叠的容器。

module Array where

import qualified Data.Vector as V
import           Data.Foldable

class Array arr where
    aRows :: arr a -> Int
    aCols :: arr a -> Int
    aData :: Foldable t => arr a -> t a

data VectorArray a = VectorArray
    { vRows :: !Int
    , vCols :: !Int
    , vData :: !(V.Vector a)}

instance Array VectorArray where
    aRows = vRows
    aCols = vCols
    aData = vData

最后一行无法编译:

• Couldn't match type ‘t’ with ‘V.Vector’
  ‘t’ is a rigid type variable bound by
    the type signature for:
      aData :: forall (t :: * -> *) a. Foldable t => VectorArray a -> t a
    at src/Array.hs:19:5
  Expected type: VectorArray a -> t a
    Actual type: VectorArray a -> V.Vector a
• In the expression: vData

我对错误信息的解释:你(程序员)给我一个V.Vector a但我(GHC编译器) 想要t a t,其中aData必须是可折叠类的实例。

现在,V.Vector是Foldable的一个实例。所以在我必须传递实例的每个函数中 Foldable 作为参数,我可以使用 V.Vector 类型的值。 我的问题是:为什么GHC没有“向上翻转” V.Vector 可折叠?有没有例子,在哪里 这样的上调会成为问题的根源吗?

PS:避免上述错误的真正好方法是从Array类中删除train.test函数 并使VectorArray成为可折叠的实例。

2 个答案:

答案 0 :(得分:8)

aData的类型表示对于调用者选择的任何t,它将返回类型为t a的值。但实现只返回Vector。 "上溯造型"如你所说,它需要将Vector转换为任何Foldable结构,但不仅没有隐含的方法来执行此操作,实际上也是不可能的:如果向量为空且用户想要一个NonEmpty列表?

答案 1 :(得分:3)

不是类型类的实例。 Int不是Num; VectorArray Foo不是Array类型是类型类的实例: Int Num VectorArray Array Vector Foldable。您无法将某些x :: Vector Foo转发为x :: Foldable,因为Foldable不适用于值。 Haskell确实有子类型(例如mempty :: forall m. Monoid m => m,但是因为Group m(如果存在这样的类)也暗示Monoid mmempty :: forall g. Group g => g,但它有点晦涩难懂并没有相关性。

你想要一个存在类型:

class Array arr where
  aRows :: arr a -> Int
  aCols :: arr a -> Int
  aData :: arr a -> (exists f. Foldable f => f a)

语法exists f. Foldable f => f a是(有点标准)psuedo-Haskell。这意味着,无论它是什么,它都是Foldable,它包含a,但你不知道包含类型到底是什么。你不能直接这样做,但你可以近距离接触:

data SomeFoldable a = forall f. Foldable f => SomeFoldable (f a)
-- Can't be a newtype: the Foldable f dictionary needs to be put somewhere
aData :: Array arr => arr a -> SomeFoldable a

存在类型的Haskell语法相当不正确,因为它使用相反的量词forall来定义它们。理由是上面创建了构造函数

SomeFoldable :: forall f. Foldable f => f a -> SomeFoldable a

"忘记" f具体是什么。

使用这些非常简单,但不如仅仅Foldable成为Array的超类一样好:

instance Array VectorArray where
    aRows = vRows
    aCols = vCols
    aData = SomeFoldable . vData

sum :: (Num a, Array arr) => arr a -> a
sum arr = case aData arr of
  SomeFoldable fa -> foldl' (+) 0 fa
-- Need to use patterns to destructure; let won't work (same for GADTs).
-- Foldable instance in scope on right side of case

-- Example:
instance Array [] where
  aRows _ = 1
  aCols = length
  aData = SomeFoldable
sum [1,2,3] == 6