Exactly. See case class method copy
, or the general concept of lenses.
In particular, if state needs changing, you'd use a State monad. Changes to that state monad can be made through lenses, which makes extracting information from "state" and changing it easy.
See also this question about the general problem that comes from a deep structure like "state" and making changes to it. The answers have good links on both lenses and zippers if you want to get deeper into that.
Let's begin with a definition for referential transparency:
An expression is said to be referentially transparent if it can be
replaced with its value without changing the behavior of a program (in
other words, yielding a program that has the same effects and output
on the same input).
What that means is that (for example) you can replace 2 + 5 with 7 in any part of the program, and the program should still work. This process is called substitution. Substitution is valid if, and only if, 2 + 5 can be replaced with 7 without affecting any other part of the program.
Let's say that I have a class called Baz
, with the functions Foo
and Bar
in it. For simplicity, we'll just say that Foo
and Bar
both return the value that is passed in. So Foo(2) + Bar(5) == 7
, as you would expect. Referential Transparency guarantees that you can replace the expression Foo(2) + Bar(5)
with the expression 7
anywhere in your program, and the program will still function identically.
But what if Foo
returned the value passed in, but Bar
returned the value passed in, plus the last value provided to Foo
? That's easy enough to do if you store the value of Foo
in a local variable within the Baz
class. Well, if the initial value of that local variable is 0, the expression Foo(2) + Bar(5)
will return the expected value of 7
the first time you invoke it, but it will return 9
the second time you invoke it.
This violates referential transparency two ways. First, Bar can not be counted on to return the same expression each time it is called. Second, a side-effect has occurred, namely that calling Foo influences the return value of Bar. Since you can no longer guarantee that Foo(2) + Bar(5)
will equal 7, you can no longer substitute.
This is what Referential Transparency means in practice; a referentially transparent function accepts some value, and returns some corresponding value, without affecting other code elsewhere in the program, and always returns the same output given the same input.
Best Answer
I'm not sure about universal definitions of purity, but from the point of view of Haskell (a language where programmers tend to care about things such as purity and referential transparency), only the first of your functions is "pure". The second version of
add
isn't pure. So in answer to your question, I'd call it "impure" ;)According to this definition, a pure function is a function that:
With this definition, it's clear your second function cannot be considered pure, since it breaks rule 2. That is, the following two programs are NOT equivalent:
and
This is because even though both functions will return the same value, function
f
will write to the database twice butg
will write once! It's very likely that writes to the database are part of the observable behavior of your program, in which case I've shown your second version ofadd
isn't "pure".If writes to the database aren't an observable part of your program's behavior, then both versions of
add
can be considered equivalent and pure. But I can't think of a scenario where writing to the database doesn't matter. Even logging matters!