如何在此声明中解析类型

时间:2014-09-02 15:17:50

标签: haskell types functional-programming monads monad-transformers

阅读"在48小时内为自己写一个方案"我对此页面https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Adding_Variables_and_Assignment感到困惑:

getVar :: Env -> String -> IOThrowsError LispVal
getVar envRef var  =  do env <- liftIO $ readIORef envRef
                         maybe (throwError $ UnboundVar "Getting an unbound variable" var)
                               (liftIO . readIORef)
                               (lookup var env)

我并不完全清楚该类型是如何解决的。这是我的理由:

envRef的类型为IORef [(String, IORef LispVal)],因此readIORef envRef的类型为IO [(String, IORef LispVal)]

现在LiftIOhttp://hackage.haskell.org/package/transformers-0.4.1.0/docs/Control-Monad-IO-Class.html定义为liftIO :: IO a -> m a类型,并在https://hackage.haskell.org/package/mtl-1.1.0.2/docs/src/Control-Monad-Error.html#ErrorT实施。因此liftIO $ readIORef envRef会为某些monad m返回m [(String, IORef LispVal)](这并不重要,因为我们只是立即使用<-,所以忽略monad包装器[1]。

这意味着env为[(String, IORef LispVal)],因此lookup var envMaybe IORefLispVal。对于may的Just分支,liftIO . readIORef将是m LispVal,对于某些monad m。假设整个函数返回IOThrowsError LispVal(只是ErrorT LispError IO LispVal,即IO Either LispError LispVal),那么m必须是IOThrowsError [2]。

现在如果有不同的monad变换器,我猜可能有多个liftIO在范围内具有不同的类型签名。这实际情况是否如此,如果是这样,上述推理链在多大程度上代表了Haskell如何确定类型?我对[1​​]或[2]中的推理不满意,因此有关如何确定liftIO的monad的次要问题。最后,为了做一个声明,怎么知道它的monad是什么?这是由<-之后的第一个monad决定的吗?或者其他一些方式?

1 个答案:

答案 0 :(得分:4)

当您liftIO $ readIORef envRef MonadIO m => m [(String, IORef LispVal)]时,m肯定很重要!它只能是一个实现MonadIO类型类的monad,它不能被忽略。它必须与maybe ...语句返回的monad相同。通常,在使用do表示法时,给定块内的所有语句都必须具有相同的monad,所以你可以这样做

main :: IO ()
main = do
    putStrLn "Testing"
    x <- getLine
    putStrLn x

但你不能

main :: IO ()
main = do
    putStrLn "Testing"
    x <- Just "Doesn't work!"
    putStrLn x

因为IOMaybe不在同一个monad中!最重要的是,main被指定为类型IO (),因此您无法有机会返回IO行为以外的其他内容。

如果您不确定哪种类型也可以推广,我建议将其加载到GHCi并在没有指定类型签名的情况下进行检查。我相信它最终将成为

的一部分
getVar :: (Eq a, MonadIO m, MonadError LispVal m) => IORef [(a, IORef LispVal)] -> a -> m b

因为liftIO $ readIORef envRefliftIO . readIORef表示monad必须是MonadIO的实例,为了执行lookup,您需要将密钥作为EqthrowError $ UnboundVar "..."表示它必须是MonadError LispVal的实例。没有任何内容可以使签名的其余部分具体化以返回LispVal,或者是您使用IOThrowsError的特定monad。