【问题标题】:Haskell ST Monad: No instance for (MArray (STArray s) Int (ST s1))Haskell ST Monad:没有(MArray(STArray s)Int(ST s1))的实例
【发布时间】:2017-09-11 02:09:36
【问题描述】:

过去一两个月我一直在学习 Haskell,最近解决了this 编码问题。额外的挑战是在没有额外空间和线性时间的情况下完成任务,我认为这不可能以纯粹的功能方式完成,所以我自然而然地发现了 ST monad,我认为这将是一个很好的机会了解更多信息。不管怎样,这是我写的代码:

module FindDuplicates where

import Control.Monad (foldM)
import Control.Monad.ST
import Data.Array.ST

xs = [4,3,2,7,8,2,3,1] :: [Int]

findDuplicates :: [Int] -> ST s [Int]
findDuplicates xs = do
    arr <- newListArray (1, length xs) xs :: ST s (STArray s Int Int)

    let go :: [Int] -> Int -> ST s [Int]
        go acc i = do x <- abs <$> readArray arr i
                      y <- readArray arr x
                      if y < 0
                          then return (x:acc)
                          else do writeArray arr x (-y)
                                  return acc 

    foldM go [] [1..length xs]

这个想法是使用 1 ≤ a[i] ≤ n 的前提条件,并且每个元素最多出现 2 次。但是代码给了我以下错误。

FindDuplicates.hs:14:36:
    No instance for (MArray (STArray s) Int (ST s1))
      arising from a use of ‘readArray’
    In the second argument of ‘(<$>)’, namely ‘readArray arr i’
    In a stmt of a 'do' block: x <- abs <$> readArray arr i
    In the expression:
      do { x <- abs <$> readArray arr i;
           y <- readArray arr x;
           if y < 0 then
               return (x : acc)
           else
               do { writeArray arr x (- y);
                    .... } }

我希望有人能指出我正确的方向!

【问题讨论】:

    标签: haskell monads st-monad starray


    【解决方案1】:

    在No instance for (MArray (STArray s) Int (ST s1)) 中,需要注意的最重要的一点是它讨论了两个不同的类型变量,s 和s1。除非这两个类型变量相同,否则没有 MArray 的实例。这是ST 对外部纯接口有效的重要部分。

    编译器认为涉及两个不同类型变量的原因是您在go 上放置了类型签名。该签名中的类型变量s 与findDuplicates 的签名中的类型变量s 不同。这是 Haskell 类型签名规则的固有部分 - 任何特定签名中的类型变量都与任何其他签名中的类型变量无关。

    解决此问题的最简单方法是从go 中删除签名。类型推断会得到正确的类型。

    如果你想更高级,你可以使用ScopedTypeVariables扩展来允许go上的签名与封闭定义共享类型变量:

    {-# LANGUAGE ScopedTypeVariables #-}
    module FindDuplicates where
    
    import Control.Monad (foldM)
    import Control.Monad.ST
    import Data.Array.ST
    
    xs = [4,3,2,7,8,2,3,1] :: [Int]
    
    findDuplicates :: forall s. [Int] -> ST s [Int]
    findDuplicates xs = do
        arr <- newListArray (1, length xs) xs :: ST s (STArray s Int Int)
    
        let go :: [Int] -> Int -> ST s [Int]
            go acc i = do x <- abs <$> readArray arr i
                          y <- readArray arr x
                          if y < 0
                              then return (x:acc)
                              else do writeArray arr x (-y)
                                      return acc 
    
        foldM go [] [1..length xs]
    

    顶部的 LANGUAGE pragma 启用扩展。要使用扩展,您需要在定义中使用forall 指定类型变量。 (忘记这样做是ScopedTypeVariables 无法工作的最常见原因。)

    在以findDuplicates 类型执行此操作后,它将s 存储在整个定义的范围内。当在go的类型中找到类型变量s时,不再将其视为新类型变量,而是使其匹配封闭上下文中的类型s。

    【讨论】:

    • 这很有趣!感谢您的宝贵时间。
    • @ZaferCesur,我喜欢ScopedTypeVariables,但如果您不想使用它,另一种选择是让go 将数组作为参数。
    猜你喜欢
    • 1970-01-01
    • 2011-10-13
    • 2019-01-23
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 2013-08-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多