2017-07-03 10 views
6

私は小さなインタプリタを書く必要があるプロジェクトに取り組んでいます。命令は単純なツリー構造を持ち、コマンドの1つは実行を中止する効果があります。下の例では、 "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

唯一のジャンプ命令が「停止」の場合、「例外」を使用して簡単に行うことができます。それとは別に、継続モナドは一般的に通訳者にはあまり適していません。 – Alec

+0

これは問題ありません。上記のように 'k 'を変更する必要がなければ、リスト全体に対して' traverse_'や 'mapM_'を使ってみることができます。 'k()'は '' early return ''の効果のために '' k()>> ... 'と同じであるので、リスト全体を(遅延的に)トラバースするのは同等でなければならないことに注意してください。 – chi

答えて

7

はい、正確にはControl.Monad.Contが適切なユースケースのようです。

あなたはほぼ確実にこのことを認識しているが、他の読者のために、それはそうのようなIO()に命令のリストから関数であったように、あなたがinterpretを書いた場合のことを綴る価値がある:

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() 

foobarおよびbazがすべて印刷されます。あなたが必要としたのは、計算全体を中止してすぐに値を返すことを可能にするエスケープ・メカニズムでした。これは正確にcallCCが提供するものです。名前付き計算(コード内でk)を呼び出すと、そのレベル/レイヤだけでなく、計算全体から脱出することができます。

ここで、ContTの適切なユースケースを正確に見つけたと思います。

関連する問題