do on a single expression accomplishes nothing whatsoever: it's just a syntactic herald indicating that you could use action sequencing here (which would require a monad), but if you don't do that then it has just the same effect as wrapping an expression in layers of redundant parentheses.
So your example session is equivalent to this:
Prelude> 0
0
Prelude> return 0
0
Prelude> Just 0
Just 0
Prelude> return (Just 0)
Just 0
Prelude> Nothing
Nothing
Prelude> return Nothing
Nothing
Now the question is why does return not accomplish anything here. Well, actually it does, you just don't see it because GHCi is hiding implementation details. GHCi has two fundamentally different modes of interactive evaluation:
IO actions are executed, then the result printed.
- Everything else is
printed as it is.
Crucially, GHCi tries rather hard to interpret everything as an IO action before proceeding with 2. So if you give it an ambiguous expression like return 0, it notices that one possible instantiation is IO Integer. It immediately defaults to that, executes this side-effect free action and all you see in the result is thus 0. This only happens for IO though, not for any other monads:
Prelude> return 0 :: IO Integer
0
Prelude> return 0 :: Maybe Integer
Just 0
Prelude> return 0 :: [] Integer
[0]
GHCi just happens to default to IO when the monad in ambiguous, but that wouldn't happen otherwise in Haskell as such.