3

閱讀解決型「自己寫的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 envRefIO [(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返回m [(String, IORef LispVal)]對於一些monad m(這並不重要,因爲我們只是立即使用<-,所以忽略monad包裝)[1]。

這意味着env是[(String, IORef LispVal)],所以lookup var envMaybe IORefLispVal。對於可能的Just分支,liftIO . readIORef將是m LispVal,對於某些monad來說也是如此。鑑於整個函數返回IOThrowsError LispVal(這只是ErrorT LispError IO LispVal,即IO Either LispError LispVal),所以m必須是IOThrowsError [2]。

現在,如果有不同的monad變換器,我想可能會有一個以上的liftIO具有不同類型的簽名。實際情況是這樣嗎?如果是這樣,上面的推理鏈在多大程度上代表了Haskell如何確定類型?我對[1​​]或[2]中的推理不滿意,因此還有一個關於如何確定liftIO的monad的第二個問題。最後,對於do語句,怎麼知道它在哪個monad中?這是由<-之後的第一個monad決定的嗎?或者以其他方式?

回答

4

當你有liftIO $ readIORef envRef,與該類型MonadIO m => m [(String, IORef LispVal)],該m當然重要!它只能是一個實現類型化類型的單子,不能被忽略。它必須與maybe ...聲明返回的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意味着單子必須是MonadIO一個實例,爲了執行lookup,你需要你的鑰匙是一個實例的EqthrowError $ UnboundVar "..."意味着它必須是MonadError LispVal的實例。沒有任何東西可以使簽名的其餘部分返回LispVal s,或者是您使用的特定monad IOThrowsError

+0

謝謝。所以do子句的返回必須與語句的返回類型相匹配,這意味着'liftIO'需要返回'IOThrowsError'值(因爲一個是do子句的返回值,另一個是< - 應用於它的值)。但是第一個'liftIO'在'IO [(String,IORef LispVal)]'上運行,所以我不知道它的結果類型是什麼。 – 2014-09-02 15:56:39

+0

'liftIO'的類型是'MonadIO m => IO a - > m a'。由於您的'IOThrowsError a'類型只是'ErrorT LispVal IO a',而'MonadIO m => ErrorT ema'是'MonadIO'的一個實例,所以可以將'''''''''''''''' - > ErrorT LispVal IO a'。 – bheklilr 2014-09-02 16:18:30