That's why I like ocaml and racket (or scheme). Functional, but you don't need to jump through hoops to do basic stuff like I/o.
Or at least that was my experience.
main = do
putStrLn "hello world"
more
more = putStrLn "hello again, still in IO"
If you want to, you can break the rule and do I/O from non-IO functions, by using trace: import Debug.Trace
add :: Int -> Int -> Int
add a b =
trace ("adding " ++ show a ++ " and " ++ show b)
(a + b)
or unsafePerformIO: import System.IO.Unsafe
add :: Int -> Int -> Int
add a b = unsafePerformIO $ do
appendFile "debug.log" ("adding " ++ show a ++ " and " ++ show b ++ "\n")
return (a + b)
Not exactly white-hot hoops of fire ? :)