I was reading the post A Modern Architecture for FP that included a code snippet that the author wanted to decompose further. I don't know Haskell but I recognize enough to know that I've written many functions like this one, combining related logging, metrics, and business logic in one function:
saveFile :: Path -> Bytes -> IO Unit saveFile p f = do log ("Saving file" ++ show (name p) ++ " to " ++ show (parentDir p)) r <- httpPost ("cloudfiles.fooservice.com/" ++ (show p)) f if (httpOK r) then log ("Successfully saved file " ++ show p) else let msg = "Failed to save file " ++ show p in log msg *> throwException (error msg)
I understand that Haskell, with only pure functions, requires special techniques to have side effects. (I am still studying up on monads and category theory to see why they matter.) While Clojure doesn't require pure functions, I still want to know how to decompose this method further.
What is the most Clojure-idiomatic way to split a function like the above? I don't understand enough about monads or Haskell to know how to apply that article to Clojure; I can see two other general possibilities:
- Write multiple functions for each composite function, each new function representing a small unit of logging, metrics publication, business logic, etc. I have one call tree that composes those all into the actual component behavior I want, and another (?!) call tree composing only the pure-function version of the component (if desired). This seems unreasonable for any component of scale.
- Write composite functions as shown above and dynamically bind no-op components when you want to get a pure function.
The second seems the most reasonable when writing enterprise software but that doesn't actually decompose anything.