let x = putStrLn "hello"
in do x ; x
is equivalent to: do putStrLn "hello" ; putStrLn "hello" let x = putStrLn "hello"
in do x ; x
is equivalent to: do putStrLn "hello" ; putStrLn "hello"See: http://conal.net/blog/posts/the-c-language-is-purely-functio...
https://downloads.haskell.org/~ghc/latest/docs/html/users_gu...
Haskell also needs a runtime to execute its evaluation strategy which is not call stack based, but graph reduction based.
Haskell isn't pure. A truly pure programming language would be completely useless as it wouldn't be able to actually manipulate state at all.
The main function returns a _description_ of the actions it's going to do; it doesn't actually execute those actions. You can pass these descriptions around as values, extend them, match on them and what not.
Think of it like Command pattern in OOP. When you pass a command around the command doesn't actually execute. It's a value that at some later stage will be executed.
I'm still a beginner, but having to figure out when and where to flush text to the console doesn't make haskell feel different from other imperative languages.
You can't actually reason about C# programs in those terms - e.g. you can't really say whether two C# programs are equivalent except in the vacuous sense of being equal as strings or ASTs. Some basic C# functions offer only operational semantics, so you have to reason about how and when those functions are "actually executed" if you want to be able to understand programs that call those functions. E.g. you can't know whether "(foo(), foo())" is equivalent to "x = foo(); (x, x)" without thinking about "how foo() is actually executed". In contrast you can reason about Haskell programs that contain IO actions without having to understand how those IO actions are executed.