| > The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about. 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... |
> On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it.
You are missing the forest for the trees.
First of all, why you'd care that a function you're calling is logging stuff is a bit beyond me but fine. Think of something else. Maybe the function is calling memcache, or storing stuff in the database, or sending a UDP packet to a message bus, or is querying the location service. Surely you can agree that there are things this function does that you don't care about if all you need is an Account given a user id, right?
These things you don't care about are called implementation details. Callers shouldn't know about them, therefore they shouldn't have to pass them in parameters.
That's what dependency injection (injection, not passing) does for you. It lets you call
instead of The first example is using dependency injection and correctly hides the implementation details of `getAccount` while not being referentially transparent.The second example is referentially transparent but exposes all kinds of private implementation details, making the callers' life very difficult, if not impossible (how are they supposed to come up with a messageBus when all they have is a user id?).