【问题标题】:Haskell: map runSTHaskell:映射 runST
【发布时间】:2012-07-24 15:35:57
【问题描述】:

我有一个类型 [ST s (Int, [Int])] 的绑定,我正在尝试使用 map 将 runST 应用于每个元素,如下所示:

name :: [ST s (Int, [Int])] --Of Course there is a real value here
map runST name

这给了我一个错误信息

Couldn't match expected type `forall s. ST s b0'
    with actual type `ST s0 (Int, [Int])'
Expected type: [forall s. ST s b0]
  Actual type: [ST s0 (Int, [Int])]
In the second argument of `map', namely `name'
In the expression: map runST name

我一定有什么误解。我知道runST and function composition,但不确定这是否适用。

感谢大家的宝贵时间!

【问题讨论】:

    标签: haskell state-monad


    【解决方案1】:

    每次使用runST 运行状态转换器时,它都会在与所有其他状态转换器分开的某个本地状态上运行。 runST 创建一个 new 状态类型并使用该类型调用其参数。所以,例如,如果你执行

    let x = runST (return ())
        y = runST (return ())
    in (x, y)
    

    那么第一个return () 和第二个return () 将有不同的类型:ST s1 ()ST s2 (),对于s1s2runST 创建的一些未知类型。

    您正在尝试使用状态类型为 s 的参数调用 runST。这不是runST 创建的状态类型,也不是您可以选择的任何其他类型。要调用runST,您必须传递一个可以具有任何 状态类型的参数。这是一个例子:

    r1 :: forall s. ST s ()
    r1 = return ()
    

    因为r1是多态的,所以它的状态可以有任何类型,包括runST选择的任何类型。您可以将runST 映射到多态r1s 的列表(使用-XImpredicativeTypes):

    map runST ([r1, r1] :: [forall t. ST t ()])
    

    但是,您不能将 runST 映射到非多态 r1s 的列表上。

    map runST ([r1, r1] :: forall t. [ST t ()]) -- Not polymorphic enough
    

    forall t. [ST t ()] 类型表示所有列表元素的状态类型为t。但是它们都需要具有独立的状态类型,因为每个状态类型都会调用runST。这就是错误消息的含义。

    如果可以为所有列表元素赋予相同的状态,那么您可以调用一次runST,如下所示。不需要显式类型签名。

    runST (sequence ([r1, r1] :: forall t. [ST t ()]))
    

    【讨论】:

      【解决方案2】:

      您的name 不够多态。你的陈述

      name :: [ST s (Int, [Int])]
      

      表示“返回 (Int, [Int]) 的有状态计算列表,它们完全相同 s'。但是看runST的类型:

      runST :: (forall s. ST s a) -> a
      

      这种类型的意思是“一个进行有状态计算的函数,其中s 可以是你能想象到的任何东西”。这些类型的计算不是一回事。最后:

      map runST :: [forall s. ST s a] -> [a]
      

      你看,你的列表应该比现在包含更多的多态值。 s 类型在列表的每个元素中可能不同,它可能与 name 中的类型不同。更改name 的类型签名,应该一切正常。它可能需要启用一些扩展,但 GHC 应该能够告诉您哪些扩展。

      【讨论】:

        【解决方案3】:

        我将尝试解释runST的类型的原因:

        runST :: (forall s. ST s a) -> a
        

        为什么它不像这个简单的:

        alternativeRunST :: ST s a -> a
        

        请注意,这个 alternativeRunST 本来可以为您的程序工作。

        alternativeRunST 还允许我们从 ST monad 中泄漏变量:

        leakyVar :: STRef s Int
        leakyVar = alternativeRunST (newSTRef 0)
        
        evilFunction :: Int -> Int
        evilFunction x =
          alternativeRunST $ do
            val <- readSTRef leakyVar
            writeSTRef leakyVar (val+1)
            return (val + x)
        

        然后你可以进入 ghci 做:

        >>> map evilFunction [7,7,7]
        [7,8,9]
        

        evilFunction 不是引用透明的!

        顺便说一句,要自己尝试一下,这里是运行上述代码所需的“坏 ST”框架:

        {-# LANGUAGE GeneralizedNewtypeDeriving #-}
        import Control.Monad
        import Data.IORef
        import System.IO.Unsafe
        newtype ST s a = ST { unST :: IO a } deriving Monad
        newtype STRef s a = STRef { unSTRef :: IORef a }
        alternativeRunST :: ST s a -> a
        alternativeRunST = unsafePerformIO . unST
        newSTRef :: a -> ST s (STRef s a)
        newSTRef = ST . liftM STRef . newIORef
        readSTRef :: STRef s a -> ST s a
        readSTRef = ST . readIORef . unSTRef
        writeSTRef :: STRef s a -> a -> ST s ()
        writeSTRef ref = ST . writeIORef (unSTRef ref)
        

        真正的runST 不允许我们构造这样的“邪恶”函数。它是如何做到的?这有点棘手,见下文:

        尝试运行:

        >>> runST (newSTRef "Hi")
        error:
            Couldn't match type `a' with `STRef s [Char]'
        ...
        
        >>> :t runST
        runST :: (forall s. ST s a) -> a
        >>> :t newSTRef "Hi"
        newSTRef "Hi" :: ST s (STRef s [Char])
        

        newSTRef "Hi" 不适合 (forall s. ST s a)。使用一个更简单的示例也可以看出,GHC 给了我们一个非常好的错误:

        dontEvenRunST :: (forall s. ST s a) -> Int
        dontEvenRunST = const 0
        
        >>> dontEvenRunST (newSTRef "Hi")
        <interactive>:14:1:
            Couldn't match type `a0' with `STRef s [Char]'
              because type variable `s' would escape its scope
        

        注意我们也可以写

        dontEvenRunST :: forall a. (forall s. ST s a) -> Int
        

        这相当于像我们之前所做的那样省略了forall a.

        注意a 的范围比s 的范围大,但在newSTRef "Hi" 的情况下,它的值应该取决于s。类型系统不允许这样做。

        【讨论】:

        • @yairchu 您能否详细说明为什么它会以dontEvenRunST 失败的原因在这里解释:en.wikibooks.org/wiki/Haskell/Existentially_quantified_types 我看到的每篇文章都提到它与参数之间的状态类型不匹配有关和返回类型,但是您表明即使返回类型是其他类型(例如Int),它仍然不会进行类型检查。
        猜你喜欢
        • 1970-01-01
        • 2020-05-16
        • 2011-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-02
        • 2012-04-01
        相关资源
        最近更新 更多