2017-07-03 77 views
6

我正在做一個項目,需要我寫一個小型的解釋器。這些指令具有簡單的樹形結構,其中一個命令具有暫停執行的效果。所以在下面的例子中,「baz」從不打印。這是對ContT的適當使用嗎?

import Control.Monad.Cont 

data Instruction = Print String | Halt | Block [Instruction] 
    deriving (Eq, Show) 

instructions = 
    [ Print "foo" 
    , Block 
    [ Print "bar" 
    , Halt 
    ] 
    , Print "baz" 
    ] 

main :: IO() 
main = runContT (callCC $ interpret instructions) 
       (const $ pure()) 

interpret []  k = pure() 
interpret (a:as) k = case a of 
    Print str -> liftIO (putStrLn str) >> interpret as k 
    Block ins -> interpret ins k  >> interpret as k 
    Halt  -> k() 

這是我第一次在我的一個項目中看到ContT的潛在用途。我想知道這是否適合使用它,或者我可能忽略了一個更簡單的解決方案。

+0

如果你唯一的跳躍指令是「停止」,你可以使用'Except'更簡單。除此之外,延續monad並不適合於一般的口譯人員。 – Alec

+0

這很好。如果我們不需要改變'k',如上所述,我們可以嘗試在整個列表中使用'traverse_'或'mapM_'。請注意,由於「早期返回」效應,'k()'與'k()>> ...'是相同的,因此遍歷整個列表(懶惰)應該是等價的。 – chi

回答

7

是的,這看起來恰恰是Control.Monad.Cont合適的用例。

你幾乎肯定是意識到了這一點,但對於其他的讀者,這是值得拼寫出來,如果你寫interpret,這樣,就從指令列表的功能的IO()像這樣:

main :: IO() 
main = interpret instructions 

interpret :: [Instruction] -> IO() 
interpret []  = pure() 
interpret (a:as) = case a of 
    Print str -> putStrLn str >> interpret as 
    Block ins -> interpret ins >> interpret as 
    Halt  -> pure() 

然後foobarbaz都會打印出來。你需要的是一種轉義機制,可以讓你中止整個計算並立即返回一個值。這正是callCC提供的。調用指定的計算(您的代碼中的k)可讓您逃避整個計算,而不僅僅是該級別/層。我相信,你已經找到了合適的ContT用例。

相關問題