How to fix writing a list content in a file - haskell

I have a function which contains a list. I want just to write that list content in a file from main after user input.
putTodo :: (Int, String) -> IO ()
putTodo (n, todo) = putStrLn (show n ++ ": " ++ todo)
prompt :: [String] -> IO ()
prompt todos = do
putStrLn "The list contains:"
mapM_ putTodo (zip [0..] todos)
putStrLn " "
command <- getLine
getCommand command todos
What I tried:
main = do
outh <- openFile "agenda.txt" WriteMode;
hPutStrLn outh prompt[]
-- hPutStrLn outh (show prompt[])
-- hPrint (show prompt[])
hClose outh;
Thank you.

Your code contains a couple of errors / problems:
prompt[] isn't valid (in main) - this should be prompt
hPutStrLn expects a String as its second argument, but you provide IO()
getCommand is not defined
What you need is:
a list of todos (possibly returned by a function)
a function that converts this list of todos to a string
hPutStrLn to print this string to the output file
Here's a simple version with a hard-coded list of todos (my Haskell isn't very advanced, so this could probably be done in a much more elegant way):
import System.IO
type Todo = (Int, String)
todoToString :: Todo -> String
todoToString (idx, name) = (show idx) ++ " : " ++ name
todosToString :: [Todo] -> String
todosToString todos = foldl (\acc t -> acc ++ "\n" ++ (todoToString t)) "" todos
allTodos :: [Todo]
allTodos = [(1, "Buy milk"), (2, "Program Haskell")]
main = do
outh <- openFile "agenda.txt" WriteMode;
hPutStrLn outh (todosToString allTodos);
hClose outh;

Related

Haskell -- Main.hs:18:5: parse error on input ‘putStrLn’

module Main where
reportResults :: [String] -> [Int] -> IO ()
reportResults fileNames exitCodes = do
putStrLn "All Files"
putStrLn "---------"
putStr.unlines $ map (" " ++) fileNames
putStrLn ""
let problems = filter (\p -> fst p /= 0) $ zip exitCodes fileNames
putStrLn "Problematic Files"
putStrLn "-----------------"
mapM_ (putStrLn . showProblem) problems
where showProblem :: (Int, String) -> String
showProblem (c, f) = " " ++ show c ++ " - " ++ f
putStrLn "Done!" -- "Parse error on input...". OK if this line is removed.
main :: IO ()
main = do
let fileNames = ["A", "B", "C", "D"]
let exitCodes = [0, 1, 2, 3]
reportResults fileNames exitCodes
The code works fine if I comment out, or remove, the offending line (line 18), but I would really like to retain it and also understand what I'm doing wrong. After trying many permutations and searching heaps, I still can't crack it. Any help would be greatly appreciated.
You can define the showProblem function in a let clause, so:
reportResults :: [String] -> [Int] -> IO ()
reportResults fileNames exitCodes = do
putStrLn "All Files"
putStrLn "---------"
putStr.unlines $ map (" " ++) fileNames
putStrLn ""
let problems = filter (\p -> fst p /= 0) $ zip exitCodes fileNames
putStrLn "Problematic Files"
putStrLn "-----------------"
let showProblem (c, f) = " " ++ show c ++ " - " ++ f
mapM_ (putStrLn . showProblem) problems
putStrLn "Done!" -- "Parse error on input...". OK if this line is removed.

'getLine' not working as intended, being skipped

I am fairly new to Haskell and am writing some simple text/file manipulation functions. I am currently attempting to modify a string before adding it to a file.
I have a function 'insertChar' which adds a character at any given position in a string (using the solution of problem 21 from 99 problems in Haskell https://wiki.haskell.org/99_questions/21_to_28).
import System.Environment
import System.Directory
import System.IO
import Data.List
main :: IO ()
main = do
putStrLn "Insert a string"
word <- getLine
putStrLn "Insert a char"
char <- getChar
putStrLn "Insert a position"
pos <- getLine *line which is skipped*
let x = (read pos :: Int) *converts string into int*
putStrLn "Adding char to list..."
let newS = [(insertChar char word x)] *result of insertChar is set to
newS*
putStrLn "Printed list: "
print (newS) *print new string*
putStrLn "Insert file name"
file <- getLine
putStrLn "Adding new string to file..."
add file newS
insertChar :: a -> [a] -> Int -> [a]
insertChar x ys 1 = x:ys
insertChar x (y:ys) n = y:insertChar x ys (n-1)
add :: String -> [String] -> IO ()
add fileName [item] = appendFile fileName item
The user is asked to enter a string, then a character they wish to add to this string and finally the position in the string where they wish to add the character. I can input the string fine but when I press enter after inputting the character, the 'getLine' for inputting the position is skipped and the following error is produced;
GHCI>main
Insert a string
hello world
Insert a char
s
Insert a position
Adding char to list...
Printed list:
["*** Exception: Prelude.read: no parse
I have seen this stack overflow post; haskell -skipping getLine
and have attempted to follow that answer changing the code to;
import System.Environment
import System.Directory
import System.IO (hSetBuffering, stdin, BufferMode(NoBuffering)) *New line*
import Data.List
main :: IO ()
main = do
hSetBuffering stdin NoBuffering *New line*
putStrLn "Insert a string"
word <- getLine
putStrLn "Insert a char"
char <- getChar
putStrLn "Insert a position"
pos <- getLine
let x = (read pos :: Int)
putStrLn "Adding char to list..."
let newS = [(insertChar char word x)]
putStrLn "Printed list: "
print (newS)
putStrLn "Insert file name"
file <- getLine
putStrLn "Adding new string to file..."
add file newS
insertChar :: a -> [a] -> Int -> [a]
insertChar x ys 1 = x:ys
insertChar x (y:ys) n = y:insertChar x ys (n-1)
add :: String -> [String] -> IO ()
add fileName [item] = appendFile fileName item
However, it still produces the same error. Any clue what I am doing wrong?
Thanks to Willem Van Onsem's comment on my original question I have been able to find a solution. I added a "getLine" after the line 'putStrLn "Insert a position"' so the code now looks like;
import System.Environment
import System.Directory
import System.IO
import Data.List
main :: IO ()
main = do
putStrLn "Insert a string"
word <- getLine
putStrLn "Insert a char"
char <- getChar
putStrLn "Insert a position"
temp <- getLine *line that has been added*
pos <- getLine
let x = (read pos :: Int)
putStrLn "Adding char to list..."
let newS = [(insertChar char word x)]
putStrLn "Printed list: "
print (newS)
putStrLn "Insert file name"
file <- getLine
putStrLn "Adding new string to file..."
add file newS
insertChar :: a -> [a] -> Int -> [a]
insertChar x ys 1 = x:ys
insertChar x (y:ys) n = y:insertChar x ys (n-1)
add :: String -> [String] -> IO ()
add fileName [item] = appendFile fileName item
You imported the right stuff, but never actually called it! Try this:
main = do
hSetBuffering stdin NoBuffering
-- ...all the rest of your original main, exactly as it used to be
This way, when it gets to the getChar line, it will return from the getChar as soon as you press a key -- not wait until you press enter. The UI will make more sense, and the code will, too, because you don't need to swallow a phantom newline that the user didn't actually want to enter anyway.

How can I poll a process for it's stdout / stderrr output? Blocked by isEOF

The following example requires the packages of:
- text
- string-conversions
- process
Code:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
module Example where
import qualified Data.Text as T
import Data.Text (Text)
import Data.Monoid
import Control.Monad.Identity
import System.Process
import GHC.IO.Handle
import Debug.Trace
import Data.String.Conversions
runGhci :: Text -> IO Text
runGhci _ = do
let expr = "print \"test\""
let inputLines = (<> "\n") <$> T.lines expr :: [Text]
print inputLines
createProcess ((proc "ghci" ["-v0", "-ignore-dot-ghci"]) {std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe}) >>= \case
(Just pin, Just pout, Just perr, ph) -> do
output <-
forM inputLines (\i -> do
let script = i <> "\n"
do
hPutStr pin $ cs $ script
hFlush pin
x <- hIsEOF pout >>= \case
True -> return ""
False -> hGetLine pout
y <- hIsEOF perr >>= \case
True -> return ""
False -> hGetLine perr
let output = cs $! x ++ y
return $ trace "OUTPUT" $ output
)
let f i o = "ghci>" <> i <> o
let final = T.concat ( zipWith f (inputLines :: [Text]) (output :: [Text]) :: [Text])
print final
terminateProcess ph
pure $ T.strip $ final
_ -> error "Invaild GHCI process"
If I attempt to run the above:
stack ghci src/Example.hs
ghci> :set -XOverloadedStrings
ghci> runGhci ""
["print \"test\"\n"]
It appears to be blocking on hIsEOF perr, according to https://stackoverflow.com/a/26510673/1663462 it sounds like I shouldn't call this function unless there is 'some output' ready to be flushed / read... However how do I handle the case where it does not have any output at that stage? I don't mind periodically 'checking' or having a timeout.
How can I prevent the above from hanging? I've tried various approaches involving hGetContents, hGetLine however they all seem to end up blocking (or closing the handle) in this situation...
I had to use additional threads, MVars, as well as timeouts:
runGhci :: Text -> IO Text
runGhci _ = do
let expr = "123 <$> 123"
let inputLines = filter (/= "") (T.lines expr)
print inputLines
createProcess ((proc "ghci" ["-v0", "-ignore-dot-ghci"]) {std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe}) >>= \case
(Just pin, Just pout, Just perr, ph) -> do
output <- do
forM inputLines
(\i -> do
let script = "putStrLn " ++ show magic ++ "\n"
++ cs i ++ "\n"
++ "putStrLn " ++ show magic ++ "\n"
do
stdoutMVar <- newEmptyMVar
stderrMVar <- newMVar ""
hPutStr pin script
hFlush pin
tOutId <- forkIO $ extract' pout >>= putMVar stdoutMVar
tErrId <- forkIO $ do
let f' = hGetLine perr >>= (\l -> modifyMVar_ stderrMVar (return . (++ (l ++ "\n"))))
forever f'
x <- timeout (1 * (10^6)) (takeMVar stdoutMVar) >>= return . fromMaybe "***ghci timed out"
y <- timeout (1 * (10^6)) (takeMVar stderrMVar) >>= return . fromMaybe "***ghci timed out"
killThread tOutId
killThread tErrId
return $ trace "OUTPUT" $ cs $! x ++ y
)
let final = T.concat ( zipWith f (inputLines :: [Text]) (output :: [Text]) :: [Text])
print final
terminateProcess ph
pure $ T.strip $ cs $ final
_ -> error "Invaild GHCI process"

How to use a value produced in another do block?

I just watched a video on Haskell so I tried to play a little bit with it but I can't get to understand this (In short I want to print one random value):
import System.Random
import System.IO
randomNum = do
gen <- newStdGen
let ns = randoms gen :: [Int]
let val = take 10 ns
print $ head val
writeToFile = do
theFile <- openFile "test.txt" WriteMode
let val = randomNum;
hPutStrLn theFile ("Random number " ++ randomNum)
hClose theFile
readFromFile = do
theFile2 <- openFile "test.txt" ReadMode
contents <- hGetContents theFile2
putStr contents
hClose theFile2
The randomNum seems to work fine but when I try to put that on writeToFile it triggers an error. What can I do?
Thanks in advance!
EDIT: The error I get in the beginning is:
Prelude> :r
[1 of 1] Compiling Main ( haskell.hs, interpreted )
haskell.hs:207:48:
No instance for (Show (IO ())) arising from a use of `show'
In the second argument of `(++)', namely `show randomNum'
In the second argument of `hPutStrLn', namely
`("Random number " ++ show randomNum)'
In a stmt of a 'do' block:
hPutStrLn theFile ("Random number " ++ show randomNum)
Failed, modules loaded: none.
It looks like what you need is
randomNum = do
gen <- newStdGen
return (head (randoms gen :: [Int]))
writeToFile = do
theFile <- openFile "test.txt" WriteMode
val <- randomNum
hPutStrLn theFile ("Random number " ++ show val)
hClose theFile
You could try this instead:
import System.Random
import System.IO
writeToFile = do
gen <- newStdGen
let ns = randoms gen :: [Int]
let val = head ns;
theFile <- openFile "test.txt" WriteMode
hPutStrLn theFile ("Random number " ++ show val)
hClose theFile
readFromFile = do
theFile2 <- openFile "test.txt" ReadMode
contents <- hGetContents theFile2
putStr contents
hClose theFile2
One problem was that the do block in your randomNum did not return a value; rather, it performed the action you told it to do: print a random number. As an alternative, see Louis Wasserman's answer for a way to make randomNum actually return a value. In this answer's code, the random number generation was just moved into writeToFile.
Also notice that I shortened the code to get one random value: ns is already a list, so you can take its head right away. The take 10 was redundant.
Finally, val was an Int, which cannot be concatenated directly onto a string. Using show val converts it to a string which can be concatenated with "Random number "

Haskell --- error using SplitOn ","

getLines = liftM lines . readFile
main = do
argv <- getArgs
name <- getProgName
if not (null argv)
then do
let file = head argv
list <- getLines file
let olist = mergesort (<=) list
let splitter = splitOn "," olist
loop olist
else hPutStr stderr $ "usage: " ++ name ++ " filename"
loop a = do
line <- getLine
case line of
"help" -> putStrLn "print - prints list in alphabetical order\n\
\quit - exits program"
"print" -> do putStrLn "[print]"
mapM_ putStrLn a
putStr "\n"
"quit" -> do putStrLn "[quit]"
exitSuccess
_ -> putStrLn "invalid command"
loop a
I'm getting this error:
Couldn't match type '[Char]' with `Char'
Expected type: [Char]
Actual type: [String]
any tips?
You need to use single quotes for char constants.
See this
let splitter = splitOn ',' olist

Resources