Estoy trabajando en un proyecto que me obliga aEscribe un pequeño intérprete. Las instrucciones tienen una estructura de árbol simple y uno de los comandos tiene el efecto de detener la ejecución. Así que en el siguiente ejemplo, "baz" nunca se imprime.
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 ()
Esta es la primera vez que veo un uso potencial para ContT
en uno de mis proyectos. Me preguntaba si este es un uso apropiado o si hay una solución más simple que podría estar pasando por alto.
Respuestas
7 para la respuesta № 1Sí, este parece ser precisamente el tipo de caso de uso para el que Control.Monad.Cont
es apropiado.
Es casi seguro que eres consciente de esto, pero para otros lectores, vale la pena explicarlo si escribiste interpret
de modo que fuera una función de una lista de instrucciones a una IO ()
al igual que:
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 ()
entonces foo
bar
y baz
Todos habrían impreso. Lo que necesitaba era un mecanismo de escape que le permitiera abortar todo el cálculo e inmediatamente devolver un valor. Esto es precisamente lo que callCC
proporciona. Llamando a la computación nombrada (k
en su código) le permite escapar de todo el cálculo, no solo de ese nivel / capa.
Muy bien, ha encontrado el caso de uso adecuado para ContT aquí, creo.