用ST数组编写多态Haskell函数

时间:2013-10-13 19:39:49

标签: arrays haskell types monads

我正在尝试在任意类型的列表上创建一个函数,并进行一些计算,将中间结果存储在STArray中。基本上,我想做这样的事情(是的,这是一个愚蠢的例子):

import Control.Monad.ST
import Data.Array.ST

echoArray :: [a] -> [[a]]
echoArray input = runST $ do
    let n = length input
    buf <- newListArray (0, n-1) $ map (\x->[x]) input :: ST s (STArray s Int [a])
    getElems buf

然而,ghci(版本7.4.2)给出了这个引人注目的错误:

x.hs:7:12:
Couldn't match type `a' with `a1'
  `a' is a rigid type variable bound by
      the type signature for echoArray :: [a] -> [[a]] at x.hs:5:1
  `a1' is a rigid type variable bound by
       an expression type signature: ST s1 (STArray s1 Int [a1])
       at x.hs:7:12
Expected type: ST s (STArray s Int [a1])
  Actual type: ST s (STArray s Int [a])
In a stmt of a 'do' block:
  buf <- newListArray (0, n - 1) $ map (\ x -> [x]) input ::
           ST s (STArray s Int [a])
In the second argument of `($)', namely
  `do { let n = length input;
        buf <- newListArray (0, n - 1) $ map (\ x -> [...]) input ::
                 ST s (STArray s Int [a]);
        getElems buf }'

如果删除类型签名(“:: ST s ...”),我仍然会收到不同的错误:

x.hs:7:12:
No instance for (MArray a0 [a] (ST s))
  arising from a use of `newListArray'
Possible fix:
  add an instance declaration for (MArray a0 [a] (ST s))
In the expression: newListArray (0, n - 1)
In a stmt of a 'do' block:
  buf <- newListArray (0, n - 1) $ map (\ x -> [x]) input
In the second argument of `($)', namely
  `do { let n = length input;
        buf <- newListArray (0, n - 1) $ map (\ x -> [x]) input;
        getElems buf }'

如果我改为将三次出现的“a”更改为Char,那么我当然可以编译它。但我想要一个可以用于[Int],[Char],[Int-&gt; Int]或其他任何东西的泛型函数。

我该怎么办?谢谢!

2 个答案:

答案 0 :(得分:3)

这里的问题是a行中的类型变量buf <-实际上与a行中的echoArray :: [a] -> [[a]]不同!您可以通过启用ScopedTypeVariables并撰写

来解决此问题
echoArray :: forall a. [a] -> [[a]]

a放在echoArray主体内的类型级别的范围内。

答案 1 :(得分:0)

所以基本上,你希望你可以将buf的右侧声明为ST s (STArray s Int [a]),但如果你这样做,那么a将是一个新的类型变量独立于a签名中的echoArray。但是你希望它与a相同。

你可以使用ScopedTypeVariables,正如@DanielWagner所示。

但是有一种方法可以在不使用ScopedTypeVariablesforall的情况下完成。

函数的签名允许您建立参数和结果类型之间的关系。因此,不使用签名来约束“结果”上的类型,而是使用签名来约束函数的类型,该函数的参数在某种程度上也包含类型a。然后让类型推断来建立连接。这是您案件的一种解决方案:

echoArray :: [a] -> [[a]]
echoArray input = runST $ do
    let n = length input
    buf <- newSTListArray (0, n-1) $ map (\x->[x]) input
    getElems buf
  where newSTListArray :: (Ix i) => (i,i) -> [a] -> ST s (STArray s i a)
        newSTListArray = newListArray

你可以看到:

  1. newSTListArray简单地定义为newListArray,因此从计算的角度来看它是无用的。但是,它可以使类型更具体。
  2. 未使用ScopedTypeVariablesforalla签名中的newSTListArray未明确与a签名中的echoArray绑定,但通过推理强制相同。这可行的原因和您的代码没有的原因是签名中的a是单独的,而此处结果类型中的a与参数类型中的a相关联