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 "
Related
I tried to write a program which takes filepaths as (command line) arguments and returns the first line of each file
main = getArgs >>= ( mapM_ ( \file -> ( openFile file ReadMode >>= ( (\handle -> hGetLine handle >>= print) >> hClose ) ) ) )
I know that this doesn't look very beautiful but I am just a beginner in Haskell. I did also avoid the do notation on purpose because I just don't feel very comfortable with her (yet).
So the Code above compiles and returns an error for invalid file paths, and nothing (i.e. especially not the first line of a file) for valid paths.
I must confess that I have pretty much no idea what I did wrong, but I made the following observation:
If I add the following to check which parts still get executed
main = getArgs >>= ( mapM_ ( \file -> ( openFile file ReadMode >>= ( (\handle -> hGetLine handle >>= print) >> (const $ putStr "Hello1") >> hClose >> (const $ putStr "Hello2") ) ) ) )
the program prints only the second "Hello", this reminded me of the type signature of (>>):
(>>) :: Monad m => m a -> m b -> m b
so taking into perspective that only something of the type of the second argument gets returned, maybe the first argument is just ignored?
But the first argument against this theory is that such a function would not seem to be very useful (at least not in the context of the IO Monad), and the second is that the program
main = (putStr "Hello" >> putStr "World" >> putStr "!")
returns 'HelloWorld!' as expected. Hence I must be completely on the wrong track, which is why I came here.
Thanks for your help!
I think you main error is that you messed up with the handle:
main = getArgs >>= (mapM_ (\file -> (openFile file ReadMode >>= (\handle -> (hGetLine handle >>= print) >> hClose handle) ) ) )
this way you did it >> was for the (-> handle) Monad (it's a reader monad - see there is an Monad instance for (->) c for constant c) not the IO!
So it did indeed pass the handle to both hGetLine handle >>= print and hClose but >> ignored the first resulting IO action and returned the hClose one as the result to >>
Here the effect was passing the handle!
So yes in the end the only executed IO-effect was closing the file!
It's subtle and not obvious as you seldom see/think about the reader-monad instance like this.
here is this with do notation
main = do
args <- getArgs
mapM_ (\file -> do
handle <- openFile file ReadMode
line <- hGetLine handle
print line
hClose handle) args
and I'd suggest switching to forM_ (from Control.Monad) for the args parameter:
main = do
args <- getArgs
forM_ args (\file -> do
handle <- openFile file ReadMode
line <- hGetLine handle
print line
hClose handle)
now you should make sure you close the handle - you can use bracket from Control.Exception for this:
main = do
args <- getArgs
forM_ args (\file -> do
bracket
(openFile file ReadMode)
hClose
(\h -> do
line <- hGetLine h
print line
)
)
or (as this is very common) just withFile from System.IO which does the opening/closing for you:
main = do
args <- getArgs
forM_ args (\file -> do
withFile file ReadMode
(\h -> do
line <- hGetLine h
print line
)
)
finally you don't really have to use all the handle stuff you can use the (lazy) readFile instead and be a bit safer with empty files too:
main = do
args <- getArgs
forM_ args (\file -> do
content <- readFile file
let ls = lines content
case ls of
[] -> putStrLn "no line in file"
(firstLine:_) -> putStrLn firstLine
)
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;
In the Learn You A Haskell book, there is a todo example that uses the do notation to assign the tempName and tempHandle:
do
...
(tempName, tempHandle) <- openTempFile "." "temp"
Which means that tempName and tempHandle are accessible throughout the function, that means you can do whatever you like in between the openTempFile and having to hClose at the end. However, as do notation is apparently syntactic sugar for >>= I'm trying to transform the do into bind, and finding it difficult to maintain a reference to tempHandle.
With my limited understanding of Haskell, I see a couple of options:
Store the tempHandle in the where clause so that it's accessible from the function;
Pass the tempHandle reference in the >>= alongside other data that I need in each step of the bind: (return ("Transformed Data", tempHandle)!?).
How would you do this? Am I approaching this from the wrong angle?
Edit: How would I keep a reference to handle if you transformed the do notation to >>= in the following example:
main = do
handle <- openFile "name.txt" (WriteMode)
putStrLn "Enter your name: "
name <- getLine
hPutStr handle name
hClose handle
When transformed to >>= how would I reference handle?
main =
openFile "name.txt" WriteMode >>
putStrLn "Enter your name: " >>
getLine >>=
(\name -> hPutStr handle?? name) >>
hClose handle??
do
(tempName, tempHandle) <- openTempFile "." "temp"
...
is the same as
openTempFile "." "temp" >>= \(tempName, tempHandle) ->
...
The second one is much harder to read, why would you want to use it ?
To complete your example :
main =
openFile "name.txt" WriteMode >>= \handle ->
putStrLn "Enter your name: " >>
getLine >>=
(\name -> hPutStr handle name) >>
hClose handle
The direct way to de-sugar the do-notation would be:
main =
openFile "name.txt" (WriteMode) >>= \handle ->
putStrLn "Enter your name: " >>
getLine >>= \name ->
hPutStr handle name >>
hClose handle
Note that the whole thing after \handle -> is part of the same function body so handle is in scope for all the following expressions.
I learn Haskell. It works fine:
import System.IO
main = do
h <- openFile "text.txt" ReadMode
cnt <- hGetContents h
mapM_ putStrLn $ lines cnt
hClose h
But this isn't working:
import System.IO
main = do
h <- openFile "text.txt" ReadMode
mapM_ putStrLn $ lines (cnt <- hGetContents h)
hClose h
Why my second variant isn't working? I expected both variants are equal, because the (cnt <- hGetContents h) is an expression and returns the value too.
The problem is that cnt <- hGetContents h is not an expression, it's some special syntax sugar inside do notation. This means it is a different way of writing the following normal Haskell code:
hGetContents h >>= \ cnt -> {- rest of do block -}
The part before the {- rest of the do block -} is not a whole expression here, because the rest of the do block is needed to complete the lambda's body.
You could desugar it manually to get something like:
hGetContents h >>= \ cnt -> mapM_ putStrLn (lines cnt)
or the point-free version
hGetContents h >>= mapM_ putStrLn . lines
You can tell that it's a special expression because it introduces a new identifier (cnt) that you can use in the rest of your code, outside of the expression itself. This is not something that normal Haskell expressions get to do (at least without compile-time magic).
cnt <- hGetContents h is essentially syntactical sugar for hGetContents h >>= \cnt ->.
It is not an expression, it is sugar intended for its own line in a do-block.
If you still want to keep it on one line, you can do this, though you will not be able to refer to the file's contents later on:
import System.IO
main = do
h <- openFile "text.txt" ReadMode
hGetContents h >>= mapM_ putStrLn . lines
hClose h
I am a Haskell newbie. I want to read only N characters of a text file into memory. So I wrote this code:
main :: IO()
main = do
inh <- openFile "input.txt" ReadMode
transformedList <- Control.Monad.liftM (take 4) $ transformFileToList inh
putStrLn "transformedList became available"
putStrLn transformedList
hClose inh
transformFileToList :: Handle -> IO [Char]
transformFileToList h = transformFileToListAcc h []
transformFileToListAcc :: Handle -> [Char] -> IO [Char]
transformFileToListAcc h acc = do
readResult <- tryIOError (hGetChar h)
case readResult of
Left e -> if isEOFError e then return acc else ioError e
Right c -> do let acc' = acc ++ [transformChar c]
putStrLn "got char"
unsafeInterleaveIO $ transformFileToListAcc h acc'
My input file several lines, with the first one being "hello world", and when I run this program, I get this output:
got char
transformedList became available
got char
["got char" a bunch of times]
hell
My expectation is that "got char" happens only 4 times. Instead, the entire file is read, one character at a time, and only THEN the first 4 characters are taken.
What am I doing wrong?
I acknowledge I don't understand how unsafeInterLeaveIO works but I suspect the problem here is somehow related to it. Maybe with this example you are trying to understand unsafeInterLeaveIO, but if I were you I'd try to avoid its direct use. Here is how I'd do it in your particular case.
main :: IO ()
main = do
inh <- openFile "input.txt" ReadMode
charList <- replicateM 4 $ hGetChar inh
let transformedList = map transformChar charList
putStrLn "transformedList became available"
putStrLn transformedList
hClose inh
This should just read the first 4 characters of the file.
If you are looking for a truly effectful streaming solution, I'd look into pipes or conduit instead of unsafeInterLeaveIO.