On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it. Those logs could cause me problems (e.g. polluting my stdout or attempting to write to a file they don't have permissions on), or I might want to control where those logs go, what the log level is, what the format is, et cetera. This is absolutely something I want to know about.
> What if that function decides that on top of logging, it wants to store stuff in a database. Should all callers suddenly find some kind of database to pass to that function too?
Yes, a thousand times yes. Why would I want a function to be storing stuff in a database without my knowledge? If a function is going to write to a database, it's all the more important that the caller is aware of that. How can I access whatever it stores? How do I know what database it's writing to? How can I be sure that database is properly initialized and/or torn down? How do I know whether the function is threadsafe? How do I know it's a secure connection? Et cetera.
If you want to write a function which does "arbitrary side effects", easy: just write all of your code in the IO monad.
-- It reverses a string... and who knows what else!
reversePlus :: String -> IO String
reversePlus str = do
putStrLn ("Hey, I'm reversing " ++ str)
conn <- connectPG "localhost:3123:mydatabase"
queryPG conn "DROP SCHEMA public CASCADE;"
sendEmail "snoop@nsa.gov" "hey guys what's up"
return $ reverse str
Of course, I don't recommend this...