I am a complete beginner to Haskell, although familiar to the functional paradigm in languages like Python, F#, Java, C# and C++(in a limited way).
Something that just keeps escaping me is IO in haskell. I tried several times, even learned C# and F# in between my tries of getting around it.
To be more specific I am referring to getting IO without do notation, using do notation, IO becomes trivial. It might be bad practice, but in my spare time I like to see if I can get things done in one continuous expression. As much of a bad practice as that is, it is fun.
Such an expression is usually of the sort(in pseudo-haskell):
main = getStdinContentsAsString
>>= ParseStringToDataStructureNeeded
>>= DoSomeComputations
>>= ConvertToString
>>= putStrLn
I have no problem with the last four parts. One of the reasons I learned F# was just to see if there was something i was not getting my head around aside from IO, but as soon as I had the convenient Console.ReadLine() of F# which returns a plain old String it was basically smooth sailing.
Which brings me back to another go at haskell, again stopped by the IO mechanism.
I have managed(using another question here) to get reading a int from the console, and print "Hello World!" that many times
main = (readLn :: IO Int) >>= \n -> mapM_ putStrLn $ replicate n "Hello World!"
I would like to get at least some "general use" way to just read the whole contents of the stdin(possibly multiple lines, so getContents would need to be the function of choice) as a string, and then i can process the string using other functions like unlines and then map.
Some of the things that I already tried:
As I said, getContents would be what I need(unless there is some equivalent to it).
Using logic, since
getContents :: IO String
Then I would need something that takes an IO String and returns a lain old String. Which is(as far as i know)
unsafePerformIO :: IO a -> a
However for some reason ghc is not happy:
* Couldn't match type `[Char]' with `IO (IO b)'
Expected type: String -> IO b
Actual type: IO (IO b) -> IO b
* In the second argument of `(>>=)', namely `unsafePerformIO'
In the expression: getContents >>= unsafePerformIO
Another thing I tried: this works without a problem;
main = getContents >>= putStrLn
Even though the type returned by getContents is an IO action and not the per se String that putStrLn wants
getContents :: IO String
putStrLn :: String -> IO ()
Somehow the action is magically executed and the resulting string is passed into the put function.
But then when I try to add something in, like simply appending " hello" to the input before printing it:
main = getContents >>= (++ " hello") >>= putStrLn
I suddenly get a type mismatch:
Couldn't match type `[]' with `IO'
Expected type: String -> IO Char
Actual type: [Char] -> [Char]
* In the second argument of `(>>=)', namely `(++ " hello")'
In the first argument of `(>>=)', namely
`getContents >>= (++ " hello")'
In the expression: getContents >>= (++ " hello") >>= putStrLn
Somehow the IO action isn't executed anymore(or maybe I just don't understand this).
I have also tried numerous things, with combinations of getLine
, readLn
, getContents
, unsafePerformIO
, read
, fmap
to no avail.
This is just a very basic example but it illustrates perfectly the problem that made me give up haskell several times now(and probably I'm not the only one), although the stubbornness of wanting to get my head around it, and learn what is pretty much THE functional programming language keeps me coming back.
To conclude:
Is there something I'm not getting?(99% yes)
If yes, then what?
How should I go about reading the whole stdin and processing it all in one continuous expression?(If I need only 1 line, I guess whatever the solution is, it would also work with getLine sine it is basically the sister of getContents)
Thanks in advance!
The main thing you're not considering seems to be the type of >>=
:
(>>=) :: IO a -> (a -> IO b) -> IO b
In other words, you don't need to "unwrap" IO String
to String
; the >>=
operator already hands a plain String
to its right operand (the function):
getContents >>= (\s -> ...)
-- ^ s :: String
The reason getContents >>= (++ " hello")
fails is that >>=
requires the function to return an IO ...
value, but (++ "hello") :: String -> String
.
You can fix this by adding return :: a -> IO a
into the mix:
getContents >>= (return . (++ "hello"))
This whole expression has the type IO String
. It will, when executed, read data from stdin
, append "hello"
to it, and return the resulting string.
Thus,
getContents >>= (return . (++ "hello")) >>= putStrLn
should work fine. But it's more complicated than necessary. Conceptually speaking, return
wraps a value in IO
and >>=
unwraps it again (sort of).
We can fuse the return
/ >>=
bits on the right:
getContents >>= (\s -> putStrLn (s ++ "hello"))
I.e. instead of taking getContents :: IO String
, adding "hello"
to it to form a new IO String
action, then attaching putStrLn :: String -> IO ()
to it, we wrap putStrLn
to create a new String -> IO ()
function (that appends "hello"
to its argument before handing the thing off to putStrLn
).
Now if we want to, we can get rid of s
by standard points-free tricks:
getContents >>= (putStrLn . (++ "hello"))
A note about IO
in general: The thing to keep in mind is that IO ...
is a normal Haskell type. There is no magic happening here. >>=
doesn't execute any actions; it just combines a value of type IO something
and a function to construct a new value of type IO somethingelse
.
You can think of Haskell as a pure meta-language that constructs an imperative program (i.e. a list of instructions to execute) as a data structure in memory. The only thing that's actually executed is the value bound to Main.main
. That is, it's like an imperative runtime runs your Haskell code to produce a pure value in main :: IO ()
. The contents of this value are then executed as imperative instructions:
main :: IO ()
main =
putChar 'H' >>
putChar 'i' >>
putChar '\n'
main
is bound to a data structure representing the imperative program print 'H'; print 'i'; print newline
. Running the Haskell program builds this data structure, then the runtime executes it.
This model is not complete, though: The imperative runtime can call back into Haskell code. >>=
can be used to "embed" Haskell functions in the imperative code, which can then (at runtime) inspect values, decide what to do next, etc. But all of that happens in the form of pure Haskell code; only the IO
value returned from f
in x >>= f
matters (f
itself has no side effects).
Is there something I'm not getting?(99% yes)
Yes.
If yes, then what?
An IO String
is something conceptually utterly different from a String
. The former is like a cooking recipe, the latter like a meal.
Until you feel like an expert Haskeller, you'd better forget that there's such a thing as unsafePerformIO
. That is something you should never need in normal Haskell code, only for FFI bindings to impure C code or for last-resort optimisations.
How should I go about reading the whole stdin and processing it all in one continuous expression?
main = getContents >>= putStrLn . (++ " hello")
Note that there are only two IO actions here: getContents
and putStrLn
. So you only need one bind operator to get information from one action to the other.
In between, you have the pure (++ " hello")
. That does not need any monadic bind, just function composition, to channel through information.
If you find the mixed direction of information-flow ugly, you can also use the flipped bind:
main = putStrLn . (++ " hello") =<< getContents
Alternatively, you could use a monadic bind, but you'd first need to masquerade the pure function as an IO action (an action which doesn't make use of any of the side-effect possibilities):
main = getContents >>= pure . (++ " hello") >>= putStrLn
Or you could, rather than “transforming putStrLn
to prepend " hello"
after the stuff it prints”, instead “transform getContents
to prepend " hello"
to the stuff it fetches”:
main = (++ " hello")<$>getContents >>= putStrLn
All of these are equivalent by the monad laws.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With