[Haskell-cafe] Combine `StateT` and `InputT` to maintain state on Ctrl-C interrupt

haskell at stefan-klinger.de haskell at stefan-klinger.de
Wed Aug 12 14:26:59 UTC 2015


Hi,

I'm stuck with maintaining a State in a Haskeline program that would
survive an interrupt with Ctrl-C.

The following is a MWE I have distilled out of a bigger project.  It
revolves around a simple (I guess) read-eval-print loop using
Haskeline, and a stacked StateT to maintain a state.

This MWE reads a line from the user, calculates its `length`, and adds
the length to an Int forming the state.  If the user types the special
input "sleep" the program sleeps for 5 seconds, simulating a longer
computation.  Every time the user presses Ctrl-C

  * a running computation should be interrupted, or

  * when at the prompt, the current input should be cleared.

  * In any case, the state must be maintained!

Unfortunately, Ctrl-C also clears the state.

At first I thought I had stacked the `StateT` and `InputT` in the
wrong order.  But changing from `InputT (StateT Int IO) ()` to `StateT
Int (InputT IO) ()` seems not to change anything (which really gives
me the creeps).

This message contains both versions (one commented out), they compile
with

    $ ghc --version
    The Glorious Glasgow Haskell Compilation System, version 7.10.1
    $ ghc --make -Wall -outputdir tmp -o mwe StateMwe.lhs

Example run, comments (`<--...`) added later:

    $ ./mwe

    type> arglschluargl
    Adding length of "arglschluargl"
    13

    type> lalala
    Adding length of "lalala"
    19

    type>                           <--no input, state unchanged
    Adding length of ""
    19

    type> sleep
    Sleeping 5 seconds...
    ^C                              <--Here I have hit C-c
    type>                           <--no input, state unchanged
    Adding length of ""
    0                               <--WRONG: that should be 19


Here we go:

> import Control.Monad.State.Strict
> import Control.Concurrent ( threadDelay )
> import System.Console.Haskeline


  > {-

Version one: `StateT` inside `InputT`

> main :: IO ()
> main = evalStateT (runInputT defaultSettings $ noesc repl) 0


This catches an interrupt via Ctrl-C and restarts the passed
operation.

> noesc :: MonadException m => InputT m a -> InputT m a
> noesc w = withInterrupt $ let loop = handle (\Interrupt -> loop) w in loop


The read-eval-print loop: EOF terminates, `sleep` delays, and any
other input modifies the integer state.

> repl :: InputT (StateT Int IO) ()
> repl = do x <- getInputLine "\ntype> "
>           case x of
>             Nothing -> return ()
>             Just "sleep"
>               -> do outputStrLn "Sleeping 5 seconds..."
>                     lift . lift . threadDelay $ 5 * 10^(6::Int)
>                     outputStrLn "...not interrupted"
>                     repl
>             Just t
>               -> do outputStrLn $ "Adding length of " ++ show t
>                     lift $ modify (+ length t)
>                     v <- lift get
>                     outputStrLn $ show v
>                     repl

  > -}


> {-

Version two: `InputT` inside `StateT`

> main :: IO ()
> main = runInputT defaultSettings . noesc $ evalStateT repl 0


This catches an interrupt via Ctrl-C and restarts the passed
operation.  I suspect that I have to rearrange this to accommodate the
modified stacking, but I could not come up with anything that
compiles...

> noesc :: MonadException m => InputT m a -> InputT m a
> noesc w = withInterrupt $ let loop = handle (\Interrupt -> loop) w in loop


As above, with `lift` in different places.  To get a better
understanding of what's going on, I do not want to use mtl's
lift-to-the-right-monad magic (yet).

> repl :: StateT Int (InputT IO) ()
> repl = do x <- lift $ getInputLine "\ntype> "
>           case x of
>             Nothing -> return ()
>             Just "sleep"
>               -> do lift $ outputStrLn "Sleeping 5 seconds..."
>                     lift . lift . threadDelay $ 5 * 10^(6::Int)
>                     lift $ outputStrLn "...not interrupted"
>                     repl
>             Just t
>               -> do lift . outputStrLn $ "Adding length of " ++ show t
>                     modify (+ length t)
>                     v <- get
>                     lift . outputStrLn $ show v
>                     repl

> -}



Any help would be welcome...

Stefan


--
http://stefan-klinger.de                                      o/X
                                                              /\/
                                                                \


More information about the Haskell-Cafe mailing list