阅读"在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)]
。
现在LiftIO
在http://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 env
为Maybe 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决定的吗?或者其他一些方式?
答案 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
因为IO
和Maybe
不在同一个monad中!最重要的是,main
被指定为类型IO ()
,因此您无法有机会返回IO
行为以外的其他内容。
如果您不确定哪种类型也可以推广,我建议将其加载到GHCi并在没有指定类型签名的情况下进行检查。我相信它最终将成为
的一部分getVar :: (Eq a, MonadIO m, MonadError LispVal m) => IORef [(a, IORef LispVal)] -> a -> m b
因为liftIO $ readIORef envRef
和liftIO . readIORef
表示monad必须是MonadIO
的实例,为了执行lookup
,您需要将密钥作为Eq
,throwError $ UnboundVar "..."
表示它必须是MonadError LispVal
的实例。没有任何内容可以使签名的其余部分具体化以返回LispVal
,或者是您使用IOThrowsError
的特定monad。