如何在Either中收集ST动作列表?

时间:2015-08-03 21:39:23

标签: haskell

如何将[Either ST]转换为Either [ST]然后按顺序执行操作?以下代码似乎适用于运行ST操作列表,但是当尝试在Either中生成操作列表(交换下面的test定义)时,类型不再排列。我本来希望列表的类型是相同的,所以我非常感谢对这些差异的任何解释。

{-# LANGUAGE RankNTypes #-}

import qualified Data.STRef as ST
import qualified Control.Monad.ST as ST

run :: (forall s. [ST.STRef s Int -> ST.ST s Int]) -> Int
run fs =
  ST.runST $ do
    x <- ST.newSTRef 0
    mapM_ (\f ->
        f x >>= ST.writeSTRef x
      ) fs
    ST.readSTRef x

action :: ST.STRef s Int -> ST.ST s Int
action = \x -> ST.readSTRef x >>= \y -> return (y + 1)

-- test :: Either String Int
-- test = mapM (const (return action)) [0..5] >>= \fs -> return (run fs)

test :: Int
test = run (map (const action) [0..5])

main = print test

2 个答案:

答案 0 :(得分:10)

存在更高级别类型时的类型推断是不可判定的。 GHC的类型推理算法做了一些简化的假设,这使得它不完整。其中包括:

  • GHC将假设由lambda绑定的变量是一个单一型(它不包含(领先?)forall s)

  • 当您使用多态函数时,GHC将假设其类型中的自由类型变量将在monotypes实例化

如果您礼貌地要求GHC使用特定的多种类型,则可以覆盖这两种假设。

现在,您希望您的程序如何键入支票?

  • 为了run fs键入支票,我们最好有fs :: forall s. [ST.STRef s Int -> ST.ST s Int]
  • 所以,根据上面的第一点,我们必须在绑定它的lambda上写下这种类型的签名:\(fs :: forall s. [ST.STRef s Int -> ST.ST s Int]) -> ...(使用ScopedTypeVariables
  • 现在考虑使用>>=。它的类型是Monad m => m a -> (a -> m b) -> m b。但是,我们需要a = forall s. [ST.STRef s Int -> ST.ST s Int]。所以,根据上面的第二点,我们需要给这个>>=一个类型签名,如

    ... `op` (\(fs :: forall s. [ST.STRef s Int -> ST.ST s Int]) -> ...)
        where op :: Monad m
                 => m (forall s. [ST.STRef s Int -> ST.ST s Int])
                 -> ((forall s. [ST.STRef s Int -> ST.ST s Int]) -> m b)
                 -> m b
              op = (>>=)
    
  • 现在我们遇到了一个新问题。在op的此应用程序中,第一个参数的类型为Either String (forall s. [ST.STRef s Int -> ST.ST s Int])。除非您打开(损坏的)ImpredicativeTypes,否则不允许将类型构造函数(除(->)之外)应用于多边形类型。但是,我们可以打开并继续......
  • 潜入第一个参数,我们可以看到我们需要在return :: Monad m => a -> m a实例化a = forall s. ST.STRef s Int -> ST.ST s Int。因此,我们需要为return
  • 添加另一种类型签名
  • 同样,我们需要在mapM :: Monad m => (a -> m b) -> [a] -> m [b]
  • 实例化b = forall s. ST.STRef s Int -> ST.ST s Int
  • 如果您密切关注,您会发现另一个问题:mapM的结果有类型

    Either String [forall s. ST.STRef s Int -> ST.ST s Int]
    

    (>>=)的参数必须是

    类型
    Either String (forall s. [ST.STRef s Int -> ST.ST s Int])
    

    你需要在这些之间进行转换。实际上这是一个无操作,但GHC并不够聪明,所以你必须做一个线性时间转换,比如

    liftM (\x -> map (\(y :: forall s. ST.STRef s Int -> ST.ST s Int) -> y) x)
    

    liftM除外需要另一种类型签名)

故事的道德:你可以做到这一点,但你不应该这样做。

如果你将forall隐藏在像

这样的新类型中,你通常会有更轻松的时间
newtype S s = S { unS :: forall s. ST.STRef s Int -> ST.ST s Int }

这使得在您的程序中更明确地引入了多态性的点(通过出现SunS)。

答案 1 :(得分:6)

您需要将函数包装在newtype中以保留存在量化,而不必使用ImpredicativeTypes

{-# LANGUAGE RankNTypes #-}

import qualified Data.STRef as ST
import qualified Control.Monad.ST as ST

newtype STFunc = STFunc (forall s. ST.STRef s Int -> ST.ST s Int)

run :: [STFunc] -> Int
run fs = ST.runST $ do
    x <- ST.newSTRef 0
    mapM_ (\(STFunc f) ->
        f x >>= ST.writeSTRef x
      ) fs
    ST.readSTRef x

action :: STFunc
action = STFunc $ \x -> ST.readSTRef x >>= \y -> return (y + 1)

test :: Either String Int
test = mapM (const (return action)) [0..5] >>= \fs -> return (run fs)

main = print test