I guess saving to database should be considered a side-effect? Loading something would obviously make the function not-pure, but saving? Same question for saving to cache, or writing to stdout or stderr.
Yes, for example even with an in-process database, if you apply the function a sufficient number of times, you can run out of memory/storage and an error will be raised. So the function cannot be pure.
But errors do not affect purity? Since you can always have division by zero. No errors → guaranteed to return → totality, I think. But even that cannot protect you against out of memory problems.
That’s considered a side-effect. Writing to stdout may not be a side-effect from the point of view of the caller (eg. if it’s piped into an other program, the caller program doesn’t see it) but from the point of view of your program or library it is.
Real world programs always have side effects, the interface of a program is: spawn, perform system calls, die. So there is no shame in doing side-effects.
So there is no shame in doing side-effects.
Sure! This is more a question of separation, to increase testability.
Ok, by definition a function that has side-effects cannot be called pure. About errors, it depends on your definition of pure function: does it always throw the same error for the same inputs? Then instead of throwing you can embed the error into the result and get a pure function, do you agree (think result type)?
PS Maybe I misunderstood your argument, a function can diverge without errors, so it won’t be total.
Ah ok. Maybe the problem is that I misunderstood pure = referential transparency, which might not be the case (local caching, for example, which would affect speed of calculation but not the relationship between input and output).
Call it “persistent memoization” to keep the FP purists at bay.
I’m going to answer in a different way than some of the other answers. The answer to your question is most probably “yes”, and here’s why:
“referential transparency” refers to the ability to do “equals-for-equals” reasoning. I find it useful to think of a position in a program text/AST as being a “referentially-transparent position”. And so clearly, if there is no way to observe what was stored to that database or cache, and the act of saving cannot cause errors, then an expression that saves something to a DB, is equal to a second expression that does everything else the first expression does, but does not save anything to a DB/cache.
The thing to watch out for, is when you actually can observe what was saved to the DB/cache; then equals-for-equals reasoning fails (a program that executes the save-to-DB-expression in the “position”, can distinguish between that, and a doesn’t-save-to-DB-expression that is otherwise equal in result).
I don’t hold with the idea that what matters is “functional purity”, and I don’t think it’s a useful concept. What’s useful, is understanding the equational rules that govern the code you’re writing. And having lots of such equational rules is nice, b/c it means you can rip out little bits of your program, run them in test environments (e.g. toplevel), debug/modify them, and when putting them back, have confidence that because they were equal to the original expression, they can be put back in place of that original expression.
If you want to go down this path, I would read up on capital match’s articles on event sourcing using haskell - Arnaud Bailly - Anatomy of a Haskell-based Application