2

好吧,我正在做一个问题,我使用的函数有一个刚性变量。我有一个使用数组来解决这个问题的想法。所以我想使用与我正在创建的函数具有相同刚性变量的数组,但我不知道如何使用刚性变量创建一个数组。我尝试了以下事情,但没有效果:

rearrange ::  [Int] -> [a] -> [a]

rearrange l la = elems (f 1 posarr)    
  where
    b = length l
    listarr :: Array Int Int
    listarr = listArray (1,b) l
    arra :: Array Int  c
    arra = listArray (1,b) la
    posarr :: Array Int  c
    posarr = listArray (1,b) la
    f i posarr
      | (b < i) = posarr
      | otherwise = f (i+1) (posarr // [(listarr!i,arra!i)] )

我得到的错误是刚性变量。对此有什么可能的解决方法?请给出如何使用刚性变量制作数组的想法,如我使用过的以下函数中的

4

2 回答 2

5

您遇到了麻烦,因为您写出的“内部”类型签名并不完全代表它们看起来的意思。特别是,您的使用与顶部签名c不对应,它们必须。aHaskell 抱怨这些“严格定义”的类型变量,尽管是变量,但不能相同,但因为它们是基于对什么a是“严格”的选择……它们必须!

您可以使用名为{-# LANGUAGE ScopedTypeVariables #-}where your code become

{-# LANGUAGE ScopedTypeVariables #-}

rearrange :: forall a . [Int] -> [a] -> [a]
rearrange l la = elems (f 1 posarr)
  where
    b = length l
    listarr :: Array Int Int
    listarr = listArray (1, b) l
    arra :: Array Int a
    arra = listArray (1,b) la
    posarr :: Array Int a
    posarr listArray (1,b) la
    f i posarr
      | (b < i) = posarr
      | otherwise = f (i+1) (posarr // [(listarr!i,arra!i)])

请注意,我所做的只是添加显式forall并将一些c变量更改为a. 让你ScopedTypeVariables做的是引入类型变量范围,使用forall在代码中缩进在这样一个显式签名下方的任何类型签名forall都可以重新使用其中引入的类型变量名称forall并使它们完全对应。

在检查 Haskell 如何在没有扩展的情况下解释类型签名时,这可能更有意义。特别是,在每个类型签名forall之前都有一个隐式

                                  -- is actually
foo :: [a] -> [a] -> [a]          foo :: forall a. [a] -> [a] -> [a]
foo xs ys = it where              foo xs ys = it where
  it :: [a]                         it :: forall a. [a]
  it = xs ++ ys                     it = xs ++ ys

这会强制a每个类型签名中的变量不同,因此这段代码无法编译,因为它仅在这两个as 相同时才有效。有了ScopedTypeVariables我们

foo :: forall a . [a] -> [a] -> [a]
foo xs ys = it where
  it :: [a]
  it = xs ++ ys

其中内部签名的a范围与外部签名的含义完全相同a

于 2013-11-09T13:37:01.197 回答
4

J. Abrahamson 解释了如何修复手头的错误,但请注意,这过于复杂。您可以省略所有本地类型签名1,编译器能够自行推断。(确实有一些应用程序需要本地类型签名,或者它有助于提高可读性;那么你经常需要ScopedTypeVariables. 但对于这样一个简单的功能,IMO 不是这样。)

说到不必要的复杂性——显式索引迭代你刚刚创建的数组没有任何好处listArray:它几乎等同于(但更笨拙)只是递归地破坏列表。这可以写成折叠。或者,在这种情况下,您“并行”遍历两个列表,折叠zip这些列表中的一个。事实上,你甚至不需要折叠:有一个很好的理由(//)接受索引值对列表,而不仅仅是一个对 - 因为你通常希望批量更新多个元素2

这大大简化了您的功能:

rearrange l la = elems $ posarr // zip l la
  where posarr = listArray (1, length l) la

1所以我没有被误解:我的意思只是本地类型签名。在顶层,所有东西都应该有一个签名,除了可能只在你的模块内部使用的完全琐碎的东西(但它在某种程度上也是一个本地签名)。

2这不仅仅是方便:您的解决方案实际上效率很低,因为在每个修改步骤都需要制作整个数组的副本,因此您可以安全地使用纯函数式语言访问中间结果。//立即使用多对调用省略了这一点:由于中间步骤从未暴露,GHC 可以在幕后发挥作用,像在命令式语言中(或在带有monad的 Haskell 中)一样进行破坏性更新。ST这要快得多。

于 2013-11-09T17:29:23.370 回答