使用 STArray 的两个几乎相同的函数:为什么一个需要 FlexibleContexts 而另一个不需要?

Two almost identical functions using STArray: why does one requires FlexibleContexts, and the other does not?

考虑 Haskell 函数

test :: ST s [Int]
test = do
    arr <- newListArray (0,9) [0..9] :: ST s (STArray s Int Int)
    let f i = do writeArray arr i (2*i)
                 readArray arr i 
    forM [1,2] f

test' :: ST s [Int]
test' = do
    arr <- newListArray (0,9) [0..9] :: ST s (STArray s Int Int)
    let f = \i -> do writeArray arr i (2*i)
                     readArray arr i
    forM [1,2] f

第一个需要 FlexibleContexts 在 ghci 8.10.1 上编译,第二个没有额外选项编译。为什么?

特别欢迎根据类型变量 s 的范围解释此行为的答案。作为后续,什么(如果有的话)类型签名可以添加到函数 f 以使 test 在没有 FlexibleContexts 的情况下编译?最后跟单态限制有关系吗?

您可以在 GHCi 中查看 GHC 分配给 f 的类型:

ghci> import Data.Array
ghci> import Data.Array.MArray
ghci> let arr :: STArray s Int Int; arr = undefined
ghci> :t \i -> do writeArray arr i (2*i); readArray arr i
\i -> do writeArray arr i (2*i); readArray arr i
  :: (MArray (STArray s1) Int m, MArray (STArray s2) Int m) =>
     Int -> m Int

这比您在评论中建议的类型更通用,也是需要 FlexibleContexts 的原因。

您可以添加您建议的类型签名 (Int -> ST s Int) 以避免必须使用 FlexibleContexts:

{-# LANGUAGE ScopedTypeVariables #-}

...

test :: forall s. ST s [Int]
test = do
    arr <- newListArray (0,9) [0..9] :: ST s (STArray s Int Int)
    let 
      f :: Int -> ST s Int
      f i = do
        writeArray arr i (2*i)
        readArray arr i 
    forM [1,2] f

注意作用域类型变量和forall s.在这里是必须的,因为你需要确保所有类型签名中的s指的是同一个类型变量并且不都引入新的类型变量。

单态限制以不同方式对待您的第一个和第二个版本的原因是因为它不适用于看起来像函数的东西。在您的第一个版本中 f 有一个参数,因此它看起来像一个函数,因此将获得一个通用类型。在你的第二个版本中 f 没有参数,所以它看起来不像一个函数,这意味着单态限制迫使它具有更具体的类型。