2012-05-09 33 views
4

我正在通過令人驚歎的Write Yourself a Scheme in 48 Hours工作,並完成了核心任務並想擴展它,但遇到了問題。我想要做的是讓eval函數可用於運行時,但有問題將其存儲到全局環境中。在Haskell中獲取元素輸出IO

的運行時環境的類型是:

type Env = IORef [(String, IORef LispVal)] 

Haskell的eval實現類型:

eval :: Env -> LispVal -> IOThrowsError LispVal 

全球環境類型的映射:

primitiveBindings :: IO Env 

爲它包含執行IO與純函數混合的函數。我的嘗試是運行eval設置爲主機eval部分與地球環境類似這樣的應用:

baseFun :: [(String, [LispVal] -> IOThrowsError LispVal)] 
baseFun = [("eval", unaryOp (eval (liftIO $ readIORef primitiveBindings)))] 

其中unaryOp是:

unaryOp :: (LispVal -> ThrowsError LispVal) -> [LispVal] -> ThrowsError LispVal 
unaryOp f [v] = f v 

我想那麼元素加入到全球環境,但我得到的編譯錯誤:

Couldn't match expected type `IORef a' 
     against inferred type `IO Env' 
In the first argument of `readIORef', namely `primitiveBindings' 
In the second argument of `($)', namely `readIORef primitiveBindings' 
In the first argument of `eval', namely 
    `(liftIO $ readIORef primitiveBindings)' 

它似乎的readIORef env這種模式發生Ø在代碼中,因此它不清楚爲什麼它不在這裏工作。我非常感謝我的錯誤啓發。作爲參考,我的代碼幾乎完全像final code for the original tutorial作爲參考。

謝謝

+1

如果你澄清了'ThrowsError'和'IOThrowsError'之間的關係,這將會很有用。我去看看它,看起來像'ThrowsError'是''LispError''和'IOThrowsError'''ErrorT LispError IO',粗略地說。 –

回答

4

據我所知,primitiveBindings一個全球性的環境,而是一個行動是,執行時,產生與已建立的原始綁定一個新的映射。也許更好的名字應該是createPrimitiveBindings。在沒有unsafePerformIO黑客的情況下,沒有辦法在Haskell中擁有適當的全局變量。

因此,這樣你想添加eval將使它在環境評估的一切,因爲你重新運行primitiveBindings行動。我們可以很容易地將錯誤類型的照顧,如果這確實是你想要的結果:

baseFun :: [(String, [LispVal] -> IOThrowsError LispVal)] 
baseFun = [("eval", evalInNewEnv)] 
    where evalInNewEnv args = do 
       env <- liftIO primitiveBindings 
       unaryOp (eval env) args 

正如你看到的,也沒有必要在這裏任何readIORef,因爲eval已經預計Env這只是一種類型的代名詞的IORef

但是,聽起來我想你喜歡eval在「全局」環境中工作,在這種情況下,您需要將該環境以某種方式傳遞給您,因爲正如我所提到的,沒有全局。例如,我們可以定義類似這樣的內容,它將創建一個新的環境,其中包含eval

primitiveBindingsWithEval :: IO Env 
primitiveBindingsWithEval = do 
    env <- primitiveBindings  
    bindVars env [("eval", unaryOp (eval env))] 

您可以primitiveBindingsWithEval當你想用它eval一個清新的環境,然後替換現有primitiveBindings用途。

+2

由於'unaryOp'的返回類型不支持'IO',所以你修正的類型錯誤並不完全正確。在你可以像這樣使用它之前,你需要概括一下'unaryOp'的類型。 –

+0

這個答案和benmachine後續行動是什麼使我的大腦點擊!作爲不錯的獎金,這個修復問題我不知道我有過。我真的很感謝幫助。謝謝。 –

2

假設:的unaryOp類型實際上涉及IOThrowsError到處都寫ThrowsError。然後,只是你不知道的領域已經放棄的類型去,我可以做一個關於你的意思猜測:

baseFun = [("eval", unaryOp (\lispVal -> liftIO primitiveBindings >>= flip eval lispVal))] 

確實,看起來就像是相當接近的,你想要什麼?

+1

'IOThrowsError'和'ThrowsError'是不同的類型。無可否認'unaryOp'可以變得更多態,在這種情況下,這並不重要。 –

3

您的問題(來自錯誤消息)是readIORef primitiveBindings

由於readIORef :: IORef a -> IO aprimitiveBindings :: IO (IORef [(String, IORef LispVal)])類型不對齊(通知primitiveBindings包裝在IO)。

嘗試primitiveBindings >>= readIORef

對於標題中的問題:由於IO是世界,而且haskell不公開它的實現,所以你只剩下由接口提供的接口Monad(綁定並返回)。如果需要,有人可以指導您更好地解釋這一點。

+0

更改爲'baseFun = [(「eval」,unaryOp(eval(primitiveBindings >> = readIORef)))]'給出與現在在報告中相同的類型錯誤「在第一個參數(>> =)'」 。也許我錯過了別的東西? –

2

你在混合IO和純粹的功能有點隨意。從本質上講,你有三個問題:

  1. 你想unaryOp (eval (liftIO $ readIORef primitiveBindings))有型[LispVal] -> IOThrowsError LispVal,但unaryOp返回一個類型[LispVal] -> ThrowsError LispValIOThrowsError是不一樣的ThrowsError。有幾種方法來解決這個問題,但截至目前爲止最簡單的是推廣的unaryOp類型:

    unaryOp :: (a -> b) -> [a] -> b 
    unaryOp f [v] = f v 
    

    (你或許應該也想出了一個更好的錯誤消息,當第二個參數不是單列表)。

    現在unaryOp做什麼之前做了,但是還可以返回[LispVal] -> IOThrowsError LispVal如果你給它LispVal -> IOThrowsError LispVal,這正是eval給它。到現在爲止還挺好。但是:

  2. liftIO (readIORef primitiveBindings)不進行類型檢查,因爲primitiveBindings不是IORef,這是一個IO的行動,產生IORef時運行。因此,您可以使用=<<(即readIORef =<< primitiveBindings)執行讀取IORef的操作,但實際上您不需要要求來執行此操作,因爲您要將IORef本身傳遞給eval,而不是其內容。所以你應該使用liftIO primitiveBindings來代替。但是:

  3. eval需要Env類型的參數,但liftIO primitiveBindings是類型IOThrowsError Env(實際上,它比這更普遍,但是這足夠接近)。所以你需要使用>>=do首先提取值:

    evalOp val = do 
        env <- liftIO primitiveBindings 
        eval env val 
    
    baseFun = [("eval", unaryOp evalOp)] 
    

順便說一句,解決問題的標題的語氣:在Haskell,你沒有得到的東西出來的IO。你所做的是你寫的功能,然後你把納入IO得到更多IO了。 >>=(即do表示法)和return是「提升」功能在IO值上操作的常用技術。

+1

這其實只是其他答案中突出顯示的問題的一種組合,但沒有人一次就清楚地解釋了他們三個:P –