I need help getting my head around the difference between my current OOP notion of state, and the way it would be done in a functional language like Haskell or Clojure.
To use a hackneyed example, let's say we're dealing with simplified bank account objects/structs/whatever. In an OOP language, I'd have some class holding a reference to a BankAccount, which would have instance variables for things like interest rate, and methods like setInterestRate() which change the object's state and generally return nothing. In say Clojure, I'd have a bank-account struct (a glorified hashmap), and special functions that take a bank-account parameter and other info, and return a new struct. So instead of changing the state of the original object, I now have a new one being returned with the desired modifications.
So... what do I do with it? Overwrite whatever variable was referencing the old bank-account? If so, does that have advantages over the state-changing OOP approach? In the end, in both cases it seems one has a variable that references the object with the necessary changes. Retarded as I am, I have only a vague concept of what's going on.
I hope that made sense, thanks for any help!
So... what do I do with it? Overwrite whatever variable was referencing the old bank-account?
Yes
If so, does that have advantages over the state-changing OOP approach?
Let's say the computation of whatever action you do on that struct takes a long time and something happens midway and you need to revert back to the original struct or the computation raised an error. With the interpretation you've presented to me of OO (using a reference, because you can have an immutable OO language) that data could be corrupt --it's unknown unless enough information was given from the failed function call, and lets suggest it failed badly. In a functional approach you know for sure that your original data structure is correct --because you initially made a copy.
Extend this scenario in multi-threaded applications. We can ensure that no one else is using the data structure we are since we all have our own version of it.
Additionally, we can save space by using data from the other structure that we are copying from. A classic example is when adding an element to the head of a list. If we have a pointer to the second element, and a pointer to the first element we can reference both lists with only the size of the first (see below). Without immutability we cannot guarantee this.
Presumably in the OO world you have a loop and are modifying these bank accounts over and over again in response to requests. Let's suppose you have a whole portfolio of accounts and these have type Portfolio. Then in Haskell you would write a pure function
And your main loop might read requests from standard input and keep your portfolio up to date. (The example is not much use unless you can write the portfolio as well, but it is simpler.)
and now I hope you see what happened to your mutable state: in a typical functional program, state that changes is passed as a parameter to a function. When the state changess, you make a new function call. The call is in tail position (you can look up 'proper tail call') and so it doesn't use any additional resources, and in fact when the compiler generates assembly code it generates a loop, and it will keep the pointer to the ever-changing Portfolio in a register.
This is a very toy example but I hope it gives you a little of the flavor of a functional language.
Look at Haskell, which is a pure functional language—it has no re-assignment whatsoever, as well as no other side-effects: in order to do IO, in the IO monad construct it actually replaces the RealWorld with a new instance of the world that has, e.g., new text displayed in the console.
In a pure functional style, you'll never overwrite any variable.
An analogy would be to spacetime in physics. If you consider the world as 3d, then objects don't have fixed positions - they move over time. To bring math to bear on the physical world, we therefore add a time dimension and consider the values of various properties at particular times. In doing so, we've made the objects of our study into constants. Similarly, in programming, there is a conceptual simplicity to be had by working with immutable values. Objects with an identity in the real world can be modeled as a sequence of immutable values (the states of the object at increasing times) rather than as a single value that changes.
Of course the details of how to associate the sequence of values to an "object identity" can be a little hairy. Haskell has Monads that let you model state. Functional Reactive Programming is a more literal attempt at modeling objects in the world with pure functional updates, that I think is a very promising direction for programming.
I will note that Clojure, unlike Haskell, isn't pure, and you can update variables as you suggested. If you're only updating a few variables at a high level, you'll still probably enjoy many of the conceptual simplicity benefits of functional programming.