So yeah, I definitely get that. I'm no Haskell master, but I found that my code progressed this way (assuming I was trying to do what you mentioned":
main :: IO ()
main = do
putStrLn "Please input your age:"
age = 40) (putStrLn "..." ++ (show age))
But as you get more comfortable, you get something like:
type Age = Int
-- Imagine there are constants here for youngAgeMessage, middleAgeMessage, and oldAgeMessage,
-- for simplicity they're just strings, though you could add even more descriptive aliases
-- like "OldMessage" with a quick union type like AgeMesssage = YoungAgeMessage | MiddleAgeMessage | OldAgeMessage, etc etc
makeAgeMessage :: Age -> String
makeAgeMessage age
| age = 40 = oldAgeMessage ++ show age
main :: IO ()
main = putStrLn "Please enter your age:"
>> getLine
>>= putStrLn . makeAgeMessage . read
This isn't even the final form of this code either, there are some more things you could do to make this code more axiomatic haskell. This code is approximate (like you probably can't copy and paste it, probably won't compile) but should at least show what I mean.
It's similar to early clojure and the use (and eventual love, usually, of the threading macro "->") and the unix philosophy -- once you start writing those clean functions that pass whatever they need right along, it starts getting easier (and more desirable) to pluck out the parts that don't have to be side-effecting.
Also, the type system expresiveness is just amazing -- it's what Java should have been but never got the chance to be:
data Thing = OneThing | AnotherThing | ThirdThing
Guess what a `Thing` can be? literally just those things, not even a null or anything. A lot of people say they really love/need/only use "strongly typed" languages (which means a ton of things to a ton of people), but the biggest slap in the face to me is how a language like Java (that people will reach for when they want to compare some usually weaker language to a "strongly typed" one), is a literal minefield of Null Pointer Exceptions (NPEs). "Anything can be anything or sometimes nil" is a hard pill to swallow once you've used Haskell. Also, trying to use Option everywhere feels wrong if you do it in Java, because it starts to bleed everywhere, but actually... it's absolutely right (IMO) -- imagine how much better java code could be if the default was to Option.map over things, and the second you decided to try and pull a value out, you knew you were opening yourself up to something bad, instead of just passing things around and hoping they're there or writing repetitive checks.
Now, if you see a `Maybe Thing`, you instantly know that that thing is either JUST the thing, or it's Nothing, and maybe's type is:
data Maybe a = Just a | Nothing
Type classes are also amazing, they're basically just as ergonomic as Go's interfaces, without some of the weird hangups and interface{}
All that said, I definitely get the hangup, Haskell is difficult to get started with, even to this day, but man, that hurdle is so worth. Maybe I'm just addicted to high-learning-curve things but Haskell feels good.