This always bothered me about haskell. Why is it it so hard to read a line from a file? I understand that because of jumping through all of these hula hoops we end up in situations where the compiler can suddenly magically do very cool things, but it feels like so much effort. I’m sure it’s effortless after you’ve internalized the state monad and the io monad and the writer monad and the reader monad, but until then, it still feels like a pain.
If I didn’t want to putStrLen at the end, but instead wanted to do something else, would it be so easy? I might be misunderstanding, but my impression is that putStrLn eases my way here because putStrLen wraps firstLine in the IO monad for me.
I think you might be misunderstanding. The putStrLn function takes a String and returns an IO action. What other than printing to the screen would you like to do? The last line could be anything!
main = do
content <- readFile "/tmp/test.txt"
let firstLine = head (lines content)
name <- getName
printMessage name firstLine
getName = do
putStr "What is your name? "
readLn
printMessage name line =
putStrLn ("You are " ++ name ++ " and the first line is " ++ line)
I must be misunderstanding the type constraints. My impression is that I need the do loop to end with an IO. What if I wanted to parse the text file and return the sum of a bunch of numbers in the text file? If I could figure out how to wrap the number in an IO monad again, that would make it typecheck.
Gotcha. I guess I just had too much trouble with the haskell docs. My impression was that I could find everything I needed to know about IO here, but clearly I should have used my google-fu and ended up here. Thanks for the clarification!
That definitely makes the IO monad easier to work with, although it still seems onerous to me. I think I’ll have to force myself to do more with it in order to internalize how it works before it seems simple.
It’s not hard to read lines from a file as Brian showed. Have you used Haskell before or do you just think that it’s hard to do trivial tasks like that?
My main experience with the IO monad was trying to parse commandline options, which I found deeply frustrating. Definitely part of the problem was my inexperience with haskell, since before this I had only done trivial work in it before. However, it seems unreasonable to me that it should be so complicated.
More precisely, I like that in Scala I can just look up io.Source.fromFile(“filename”).getLines (or something similar) and it hands me back an iterator, instead of an IO iterator which I have to be super careful with, especially since it isn’t clear to me how to unwrap an IO a la
val x = unwrap IO(lines)
edit: I am aware that my example of having a hard time with the IO monad is godawful haskell.
You can’t completely unwrap IO, that’s the point. You always want to wrap it back up with map or flatMap/bind/chain. That’s exactly what do-notation does:
main = do
content <- readFile "/tmp/test.txt"
-- "content" as being unwrapped
let x = lines content
-- wrap it back up with "print"
print x
read data from a file
get out of io land with that data
manipulate that data
later, output the result of manipulating the data.
The impression I have now is that it is illegal to leave IO land with that data, unless it is safely wrapped in an IO. If I wanted to do something like this, instead the right way would be to say something along the lines of
let x = read data
let y = map x manipulate
-- I don't know map syntax in haskell
do
manipulated <- y
putStrLn manipulated
it seems like a pain to have to add an extra level of mapping every time I want to manipulate something that came from outside. I have heard that this is to protect us from the outside world since it is impure, but I suppose I just haven’t internalized it.
The above example does almost the 4 things you listed. Let’s go line by line:
main = do
read data from a file
content <- readFile "/tmp/test.txt"
Now here’s the thing – content is not an IO value but it’s also not “out of IO land” because it can’t escape (the whole do expression must return an IO).
manipulate that data
let x = lines content
later, output the result of manipulating the data.
import Data.Char
shout :: String -> String
shout line = map toUpper line
getLines :: String -> IO [String]
getLines filename =
do content <- readFile filename
let ls = lines content
let shoutyLines = map shout ls
return shoutyLines
main :: IO ()
main =
do shoutyLines <- getLines "example.in"
let joinedLines = unlines shoutyLines
putStrLn joinedLines
getLines :: String -> IO [String]
getLines takes in a String and, if it is ever run, does some IO and
returns a list of Strings. In this case, if it is run, it reads all
the lines from a file into a list of Strings, converts each of those
strings to uppercase and returns (lifts it into a Monad context) the
new list.
By the way, the above code could also be written as (and it actually
gets expanded into something very similar since do notation is just syntactic sugar):
This always bothered me about haskell. Why is it it so hard to read a line from a file? I understand that because of jumping through all of these hula hoops we end up in situations where the compiler can suddenly magically do very cool things, but it feels like so much effort. I’m sure it’s effortless after you’ve internalized the state monad and the io monad and the writer monad and the reader monad, but until then, it still feels like a pain.
This has nothing to do with reading a file. There is nothing about the IO monad in the article.
Reading a file is not hard:
Done.
Where was the effort?
Since my above Haskell is actually async, let’s talk about real effort:
This also seems like a pain in the ass, I would not like to do IO things in this language either.
If I didn’t want to putStrLen at the end, but instead wanted to do something else, would it be so easy? I might be misunderstanding, but my impression is that putStrLn eases my way here because putStrLen wraps firstLine in the IO monad for me.
I think you might be misunderstanding. The
putStrLnfunction takes aStringand returns an IO action. What other than printing to the screen would you like to do? The last line could be anything!I must be misunderstanding the type constraints. My impression is that I need the do loop to end with an IO. What if I wanted to parse the text file and return the sum of a bunch of numbers in the text file? If I could figure out how to wrap the number in an IO monad again, that would make it typecheck.
I could just be badly misunderstanding do loops.
You’re after the
returnfunction. Will take any value and wrap it in IO.Gotcha. I guess I just had too much trouble with the haskell docs. My impression was that I could find everything I needed to know about IO here, but clearly I should have used my google-fu and ended up here. Thanks for the clarification!
That definitely makes the IO monad easier to work with, although it still seems onerous to me. I think I’ll have to force myself to do more with it in order to internalize how it works before it seems simple.
It’s not hard to read lines from a file as Brian showed. Have you used Haskell before or do you just think that it’s hard to do trivial tasks like that?
My main experience with the IO monad was trying to parse commandline options, which I found deeply frustrating. Definitely part of the problem was my inexperience with haskell, since before this I had only done trivial work in it before. However, it seems unreasonable to me that it should be so complicated.
More precisely, I like that in Scala I can just look up io.Source.fromFile(“filename”).getLines (or something similar) and it hands me back an iterator, instead of an IO iterator which I have to be super careful with, especially since it isn’t clear to me how to unwrap an IO a la
val x = unwrap IO(lines)
edit: I am aware that my example of having a hard time with the IO monad is godawful haskell.
You can’t completely unwrap IO, that’s the point. You always want to wrap it back up with map or flatMap/bind/chain. That’s exactly what do-notation does:
I am having trouble understanding how I do this:
read data from a file
get out of io land with that data
manipulate that data
later, output the result of manipulating the data.
The impression I have now is that it is illegal to leave IO land with that data, unless it is safely wrapped in an IO. If I wanted to do something like this, instead the right way would be to say something along the lines of
it seems like a pain to have to add an extra level of mapping every time I want to manipulate something that came from outside. I have heard that this is to protect us from the outside world since it is impure, but I suppose I just haven’t internalized it.
The above example does almost the 4 things you listed. Let’s go line by line:
Now here’s the thing –
contentis not an IO value but it’s also not “out of IO land” because it can’t escape (the whole do expression must return an IO).I understood that part. What I didn’t understand how to do was how to carry around the IO in a subsequent expression.
Maybe this will help clarify things:
getLinestakes in aStringand, if it is ever run, does some IO and returns a list ofStrings. In this case, if it is run, it reads all the lines from a file into a list ofStrings, converts each of those strings to uppercase andreturns (lifts it into a Monad context) the new list.By the way, the above code could also be written as (and it actually gets expanded into something very similar since do notation is just syntactic sugar):