I want to write a toy program that has an interactive prompt and that can save and display all previous inputs. This is my first attempt, but does not compile (using ghc):
import System.IO
import Control.Monad.State
data ProgramState = ProgramState
{ events :: [Int] } -- Placeholder for now
parse_input :: String -> State ProgramState Bool
parse_input prompt = do
putStr prompt
hFlush stdout
current_state <- get
str <- getLine
case str of
"c" -> do
put (current_state { events = [1,2,3] } ) -- this should become actual appending
return True
"l" -> return True
"q" -> return False
"quit" -> return False
"h" -> return True
_ -> do
putStrLn "Invalid input."
parse_input prompt
main :: IO ()
main = do
should_continue <- parse_input "Enter your command."
if should_continue then main else return ()
main.hs:9:5: error:
• Couldn't match type ‘IO’
with ‘StateT ProgramState Data.Functor.Identity.Identity’
Expected type: StateT
ProgramState Data.Functor.Identity.Identity ()
Actual type: IO ()
Note: line 9 is putStr prompt
The same error is given for lines 10, 12, 22, 27.
I have since thought of doing the recursion purely inside parse_input, in which case I don't seem to need the state monad. But I am still curious why I get the compilation error. Any help is appreciated, I am very new to Haskell.
You seem to be mixing values of type State s a with values of type IO a. In your main action, you call parse_input in a context expecting IO. In parse_input, you call putStr and so on in a context expecting State. That's not going to work!
The usual way to do this kind of thing is to switch from State to StateT, and import Control.Monad.IO.Class. Now, you can use
evalStateT :: StateT s m a -> s -> m a
to "lower" your loop to IO, and
-- liftIO :: IO a -> StateT s IO a
liftIO :: MonadIO m => IO a -> m a
to "lift" the IO actions to StateT within the loop. Now (untested code ahead):
-- Needed for flexible use of
-- the MonadState class.
{-# LANGUAGE FlexibleContexts #-}
import System.IO
-- You almost always want the "strict"
-- version of `StateT`; the lazy one is weird.
import Control.Monad.State.Strict
import Control.Monad.IO.Class
data ProgramState = ProgramState
{ events :: [Int] } -- Placeholder for now
-- Renaming your function to follow convention.
parseInput
:: (MonadState ProgramState m, MonadIO m)
=> String -> m Bool
parseInput prompt = do
str <- liftIO $ do
putStr prompt
hFlush stdout
getLine
current_state <- get
case str of
"c" -> do
put (current_state { events = [1,2,3] } ) -- this should become actual appending
return True
"l" -> return True
"q" -> return False
"quit" -> return False
"h" -> return True
_ -> do
liftIO $ putStrLn "Invalid input."
parseInput prompt
main :: IO ()
main = do
-- You need to supply the initial state; I've just guessed here.
should_continue <- evalStateT (parseInput "Enter your command.") (ProgramState [])
if should_continue then main else return ()
As Daniel Wagner points out, this will not preserve the state from one main run to the next. If that's your intention, you can write
main :: IO ()
main = evalStateT loop (ProgramState [])
where
loop = do
should_continue <- parseInput "Enter your command."
if should_continue then loop else return ()
If you like, you can import Control.Monad and shorten this to
main :: IO ()
main = evalStateT loop (ProgramState [])
where
loop = do
should_continue <- parseInput "Enter your command."
when should_continue loop
Final note: if you want to capture the final state of your loop, use runStateT instead of evalStateT.
Related
I want to write a text interface, which provides some default commands. This program supports tab completion of those commands.
This program also records user inputs and stores it in StateData. And now I want this program to support tab completion of those user inputs. For example:
*Main > main
> read a<tab> -- press tab and no suggestions (read is a default command)
> read abcde
...
> read a<tab> -- press tab
abcde -- suggestions
Is it possible to do that without using unsafe mechanism like IORef? Is there a way to pass updated st from loop (in repl) to replSettings startState (in repl)?
I am new to Haskeline and thanks for your time.
repl :: StateData -> IO()
repl startState = runInputT (replSettings startState) $ loop startState
where
loop :: StateData -> InputT IO ()
loop st = do
inputL <- getInputLine "> "
case inputL of
Nothing -> return ()
Just "quit" -> outputStrLn "--Exited--" >> return ()
Just ipt -> do (opt, st') <- process ipt `runStateT` st
...
loop st'
replSettings :: StateData -> Settings IO
replSettings st =
Settings
{ complete = replCompletion st,
historyFile = Just "history.txt",
autoAddHistory = True
}
replCompletion :: StateData -> CompletionFunc IO
replCompletion st = completeWordWithPrev Nothing [' '] st (\x y -> return $ completionGenerator x y)
completionGenerator :: String -> String -> StateData -> [Completion]
completionGenerator "" c st =
commandSuggestion c (updatesSuggestions st) -- I wish to update it at run time
completionGenerator p c st = ...
IORef isn’t unsafe; you’re already in IO, so it’s a perfectly reasonable way to add mutable state here.
But if you want to avoid IO, you can simply use StateT StateData IO as the underlying monad for InputT, and thus the completion function in Settings. It seems you’re already trying to use StateT anyway. Here’s a complete example that just adds every entry to a list and autocompletes them naïvely:
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (StateT, evalStateT, get, modify)
import Data.List (isPrefixOf)
import System.Console.Haskeline
type StateData = [String]
main :: IO ()
main = repl []
repl :: StateData -> IO ()
repl startState
= flip evalStateT startState
$ runInputT replSettings loop
where
loop :: InputT (StateT StateData IO) ()
loop = do
inputL <- getInputLine "> "
case inputL of
Nothing -> pure ()
Just "quit" -> outputStrLn "--Exited--"
Just ipt -> do
-- Just add each entry to the state directly.
lift $ modify (ipt :)
loop
replSettings :: Settings (StateT StateData IO)
replSettings = Settings
{ complete = replCompletion
, historyFile = Just "history.txt"
, autoAddHistory = True
}
replCompletion :: CompletionFunc (StateT StateData IO)
replCompletion = completeWordWithPrev Nothing " " completionGenerator
completionGenerator :: String -> String -> StateT StateData IO [Completion]
completionGenerator prefix suffix = do
st <- get
-- Trivial completion that just ignores the suffix.
pure $ fmap (\ s -> Completion s s True)
$ filter (prefix `isPrefixOf`) st
The completion generator could also be written using MonadState (from mtl) to insulate it from being able to access IO, and other code could likewise use this pure state while being agnostic to IO. But otherwise, since you’re already in IO in this code, StateT StateData IO / get / modify are no different than ReaderT (IORef StateData) IO / readIORef / modifyIORef.
In fact, if you put an IORef in StateData, supposing it’s a more complex record type in your code, the latter is a good way to make some parts of it mutable and others immutable.
data StateData = StateData
{ mutableThing :: !(IORef Thing)
, immutableStuff :: !Stuff
…
}
As we know main function has type IO ().
However, it is problem for me, because my program may return error. It means that I am executing from main function something like that:
ErrorT String IO ()
Of course, at this moment I have problem with type errors.
What should I do ?
args <- getArgs
s <- readFile $ head args
myFoo s
Where myFoo :: String -> ErrorT String IO ()
You need to run it with runErrorT:
runErrorT :: ErrorT e m a -> m (Either e a)
Since myFoo returns a ErrorT String IO () this will evaluate to an IO (Either String ()) which you execute in main and match on the result:
args <- getArgs
s <- readFile $ head args
result <- runErrorT (myFoo s)
case result of
Right _ -> putStrLn "ok!"
Left(err) -> putStrLn $ "Error: " ++ err
To expand on #Lee's answer, you can then use exitFailure and exitSuccess from System.Exit to return an appropriate error code to the calling process:
module Main (main) where
import Control.Monad.Error
import System.Environment
import System.Exit
myFoo :: String -> ErrorT String IO ()
myFoo = undefined
main :: IO ()
main = do
args <- getArgs
s <- readFile $ head args
result <- runErrorT (myFoo s)
case result of
Right _ -> do
putStrLn "OK"
exitSuccess
Left (e) -> do
putStrLn $ "Error: " ++ e
exitFailure
EDITED 2015-11-29: see bottom
I'm trying to write an application that has a do-last-action-again button. The command in question can ask for input, and my thought for how to accomplish this was to just rerun the resulting monad with memoized IO.
There are lots of posts on SO with similar questions, but none of the solutions seem to work here.
I lifted the memoIO code from this SO answer, and changed the implementation to run over MonadIO.
-- Memoize an IO function
memoIO :: MonadIO m => m a -> m (m a)
memoIO action = do
ref <- liftIO $ newMVar Nothing
return $ do
x <- maybe action return =<< liftIO (takeMVar ref)
liftIO . putMVar ref $ Just x
return x
I've got a small repro of my app's approach, the only real difference being my app has a big transformer stack instead of just running in IO:
-- Global variable to contain the action we want to repeat
actionToRepeat :: IORef (IO String)
actionToRepeat = unsafePerformIO . newIORef $ return ""
-- Run an action and store it as the action to repeat
repeatable :: IO String -> IO String
repeatable action = do
writeIORef actionToRepeat action
action
-- Run the last action stored by repeatable
doRepeat :: IO String
doRepeat = do
x <- readIORef actionToRepeat
x
The idea being I can store an action with memoized IO in an IORef (via repeatable) when I record what was last done, and then do it again it out with doRepeat.
I test this via:
-- IO function to memoize
getName :: IO String
getName = do
putStr "name> "
getLine
main :: IO ()
main = do
repeatable $ do
memoized <- memoIO getName
name <- memoized
putStr "hello "
putStrLn name
return name
doRepeat
return ()
with expected output:
name> isovector
hello isovector
hello isovector
but actual output:
name> isovector
hello isovector
name> wasnt memoized
hello wasnt memoized
I'm not entirely sure what the issue is, or even how to go about debugging this. Gun to my head, I'd assume lazy evaluation is biting me somewhere, but I can't figure out where.
Thanks in advance!
EDIT 2015-11-29: My intended use case for this is to implement the repeat last change operator in a vim-clone. Each action can perform an arbitrary number of arbitrary IO calls, and I would like it to be able to specify which ones should be memoized (reading a file, probably not. asking the user for input, yes).
the problem is in main you are creating a new memo each time you call the action
you need to move memoized <- memoIO getName up above the action
main :: IO ()
main = do
memoized <- memoIO getName --moved above repeatable $ do
repeatable $ do
--it was here
name <- memoized
putStr "hello "
putStrLn name
return name
doRepeat
return ()
edit: is this acceptable
import Data.IORef
import System.IO.Unsafe
{-# NOINLINE actionToRepeat #-}
actionToRepeat :: IORef (IO String)
actionToRepeat = unsafePerformIO . newIORef $ return ""
type Repeatable a = IO (IO a)
-- Run an action and store the Repeatable part of the action
repeatable :: Repeatable String -> IO String
repeatable action = do
repeatAction <- action
writeIORef actionToRepeat repeatAction
repeatAction
-- Run the last action stored by repeatable
doRepeat :: IO String
doRepeat = do
x <- readIORef actionToRepeat
x
-- everything before (return $ do) is run just once
hello :: Repeatable String
hello = do
putStr "name> "
name <- getLine
return $ do
putStr "hello "
putStrLn name
return name
main :: IO ()
main = do
repeatable hello
doRepeat
return ()
I came up with a solution. It requires wrapping the original monad in a new transformer which records the results of IO and injects them the next time the underlying monad is run.
Posting it here so my answer is complete.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
import Control.Applicative (Applicative(..))
import Data.Dynamic
import Data.Maybe (fromJust)
import Control.Monad.RWS
-- | A monad transformer adding the ability to record the results
-- of IO actions and later replay them.
newtype ReplayT m a =
ReplayT { runReplayT :: RWST () [Dynamic] [Dynamic] m a }
deriving ( Functor
, Applicative
, Monad
, MonadIO
, MonadState [Dynamic]
, MonadWriter [Dynamic]
, MonadTrans
)
-- | Removes the first element from a list State and returns it.
dequeue :: MonadState [r] m
=> m (Maybe r)
dequeue = do
get >>= \case
[] -> return Nothing
(x:xs) -> do
put xs
return $ Just x
-- | Marks an IO action to be memoized after its first invocation.
sample :: ( MonadIO m
, Typeable r)
=> IO r
-> ReplayT m r
sample action = do
a <- dequeue >>= \case
Just x -> return . fromJust $ fromDynamic x
Nothing -> liftIO action
tell [toDyn a]
return a
-- | Runs an action and records all of its sampled IO. Returns a
-- action which when invoked will use the recorded IO.
record :: Monad m
=> ReplayT m a
-> m (m a)
record action = do
(a, w) <- evalRWST (runReplayT action) () []
return $ do
evalRWST (runReplayT action) () w
return a
Given the proof of concept code below I'd like to be able to somehow perform my foo function with the ability to output the string Paul! and the possibility of getting its return value inside the InputT monad-transformer without using unsafePerformIO to remove the IO wrapper after runExceptT.
import Control.Monad.Except
import System.IO.Unsafe (unsafePerformIO)
import System.Console.Haskeline
type ErrorWithIO = ExceptT String IO
foo :: String -> ErrorWithIO String
foo "paul" = do liftIO $ putStrLn "Paul!"
return "OK!"
foo _ = throwError "ERROR!"
runRepl :: IO ()
runRepl = runInputT defaultSettings $ loop
loop :: InputT IO ()
loop = do
line <- getInputLine "> "
case line of
Nothing -> return ()
Just input -> do return $ putStrLn "asd"
case unsafePerformIO $ runExceptT $ foo input of
Left err -> outputStrLn err >> loop
Right res -> do
x <- outputStrLn . show $ res
loop
main :: IO ()
main = runRepl >> putStrLn "Goodbye!"
Am I missing something obvious here?
Since InputT IO is a MonadIO, you can use liftIO with this type:
liftIO :: IO a -> InputT IO a
So,
do ...
x <- liftIO $ runExceptT $ foo input
case x of
Left err -> ...
Right res -> ...
Alternatively, use Control.Monad.Trans.lift instead.
Problem: Process a CSV file and test a condition on it. Current code simply prints instead of testing for the condition.
Issue: Type inference fails. I do not follow why it fails.
Here's the code, less the import boilerplate.
--------------------------------------------------
has_empty_string :: [String] -> Bool
has_empty_string col =
any null col
----------------------------------------
get_hashrow :: [[String]] -> [String]
get_hashrow sheet =
-- looking at column 5
map (\row -> row !! 5) sheet
------------------------------
process_lines :: (String -> b) -> Handle -> IO ()
process_lines func inh = do
ineof <- hIsEOF inh
if ineof
then return ()
else do inpStr <- hGetLine inh
result <- func inpStr
putStrLn $ show result
process_lines func inh
------------------------------
process_lines_in_file :: (String -> b) -> FilePath -> IO ()
process_lines_in_file func filename =
do inh <- openFile filename ReadMode
process_lines func inh
----------------------------------------
test_csv_row :: String -> Bool
test_csv_row row =
has_empty_string ( get_hashrow ( readCSV row))
----------------------------------------
main :: IO ()
main = do
[filename] <- getArgs
process_lines_in_file test_csv_row filename
return ()
And here's the error:
Couldn't match expected type `b' against inferred type `IO a'
`b' is a rigid type variable bound by
the type signature for `process_lines' at content-hash-p.hs:29:28
In a stmt of a 'do' expression: result <- func inpStr
In the expression:
do { inpStr <- hGetLine inh;
result <- func inpStr;
putStrLn $ show result;
process_lines func inh }
In the expression:
if ineof then
return ()
else
do { inpStr <- hGetLine inh;
result <- func inpStr;
putStrLn $ show result;
.... }
(In the future, please include the import boilerplate.)
Type inference is not failing -- since you're not asking the compiler to do any type inference! However, type-checking is failing. Let's see why.
You claim process_lines :: (String -> b) -> Handle -> IO (). Experienced Haskeller's will already be shuddering at this type. Why? This type claims that its first argument can be any function at all which does something to a String. But this is an odd claim to make, since the return type of this function doesn't appear anywhere else in the type of process_lines -- meaning, we can call this function, but never use its result! Thanks to laziness, this means that the call will never actually happen.
So it's a weird type. Let's see if we can take the argument above and find out where it fails in your code; that should help point to the problem.
process_lines func inh = do
ineof <- hIsEOF inh
if ineof
then return ()
else do inpStr <- hGetLine inh
result <- func inpStr -- HERE
putStrLn $ show result
process_lines func inh
Take a look at the line marked HERE. This is the only occurrence of func in our implementation. According to the argument above, we can never use the output of func, yet here we seem to be using the output of func. What type are we using it at? Well, we're using it at an IO {- something -} type, since it's in a do-block; furthermore, since we bind result and then call show result, the {- something -} must be some type that we can call show on -- that is, a member of the Show class. So the type of func is not as unrestricted as String -> b; it's the more restricted Show b => String -> IO b. A similar argument applies to process_lines_in_file, so that its updated type ought to be process_lines_in_file :: Show b => (String -> IO b) -> FilePath -> IO ().
(Indeed, if you leave off the type signatures, type inference will infer exactly these types for you.)
Now that process_lines_in_file demands a function that does IO, we can no longer pass test_csv_row as-is. You can choose either to call process_lines_in_file (return . test_csv_row) filename in main or to change the implementation of test_csv_row to call return (which does the trivial IO action: no input or output, just do a pure computation and pretend it did IO).
With these changes, the code compiles.