I'd say that the best thing to ask is not how we would call it, but how we would analyze such a piece of code. And my first key question in such an analysis would be:
- Does the side effect depend on the argument to the function, or the result on the side effect?
- No: The "effectful function" can be refactored into a pure function, an effectful action, and a mechanism for combining them.
- Yes: The "effectful function" is a function that produces a monadic result.
This is straightforward to illustrate in Haskell (and this sentence is only half a joke). An example of the "no" case would be something like this:
double :: Num a => a -> IO a
double x = do
putStrLn "I'm doubling some number"
return (x*2)
In this example the action that we take (print the line "I'm doubling some number"
) has no impact on the relationship between x
and the result. This means we can refactor it this way (using the Applicative
class and its *>
operator), which shows that the function and the effect are in fact orthogonal:
double :: Num a => a -> IO a
double x = action *> pure (function x)
where
-- The pure function
function x = x*2
-- The side effect
action = putStrLn "I'm doubling some number"
So in this case I, personally, would say that it's a case where you can factor out a pure function. A lot of Haskell programming is about this—learning how to factor out the pure parts from the effectful code.
An example of the "yes" sort, where the pure and the effectful parts are not orthogonal:
double :: Num a => a -> IO a
double x = do
putStrLn ("I'm doubling the number " ++ show x)
return (x*2)
Now, the string that you print depends on the value of x
. The function part (multiply x
by two), however, doesn't depend on the effect at all, so we can still factor it out:
logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
logger a
return (function a)
double x = logged function logger
where function = (*2)
logger x putStrLn ("I'm doubling the number " ++ show x)
I could go on spelling out other examples, but I hope this is enough to illustrate the point I started with: you don't "call" it something, you analyze how the pure and the effectful parts relate and factor them out when it is to your advantage.
This is one of the reasons Haskell uses its Monad
class so extensively. Monads are (among other things) a tool for performing this sort of analysis and refactoring.
writeToDatabase
fails it could trigger an exception thus making your secondadd
function produce an exception sometimes even if called with the same arguments that before didn't have problems... most of the time having side effects introduces this kind of error-related conditions that break "input-output purity".F(x)
is defined to returntrue
if it's called with the same argument as the previous call. Clearly with the sequence{1,2,2} => {undefined, false, true}
this is deterministic, yet it gives different outputs forF(2)
.