In an answer to this question (written by Pete) there are some considerations about OOP versus FP. In particular, it is suggested that FP languages are not very suitable for modelling (persistent) objects that have an identity and a mutable state.
I was wondering if this is true or, in other words, how one would model objects in a functional programming language. From my basic knowledge of Haskell I thought that one could use monads in some way, but I really do not know enough on this topic to come up with a clear answer.
So, how are entities with an identity and a mutable persistent state normally modelled in a functional language?
Here are some further details to clarify what I have in mind. Take a typical Java application in which I can (1) read a record from a database table into a Java object, (2) modify the object in different ways, (3) save the modified object to the database.
How would this be implemented e.g. in Haskell? I would initially read the record into a record value (defined by a data definition), perform different transformations by applying functions to this initial value (each intermediate value is a new, modified copy of the original record) and then write the final record value to the database.
Is this all there is to it? How can I ensure that at each moment in time only one copy of the record is valid / accessible? One does not want to have different immutable values representing different snapshots of the same object to be accessible at the same time.
3
The usual way to go about state changes in a pure language like Haskell is to model them as functions that take the old state and return a modified version. Even for complex objects, this is efficient because of Haskell’s lazy evaluation strategy – even though you are syntactically creating a new object, it is not copied in its entirety; each field is evaluated only when it is needed.
If you have more than a few local state changes, things can become clumsy, which is where monads come in. The monad paradigm can be used to encapsulate a state and its changes; the textbook example is the State
monad that comes with a standard Haskell install. Note, however, that a monad is nothing special: it’s just a data type that exposes two methods (>>=
and return
), and meets a few expectations (the ‘monad laws’). Under the hood, the State monad does exactly the same: take the old state and return a modified state; only the syntax is nicer.
2
I’m not a functional language developer, so please shoot me down in flames if I have this wrong, but if right, it might be an interesting analogy.
I was once told that Excel is essentially a functional language. I’m not talking about VBA and so on, I’m talking about what happens on sheet.
So you have inputs, which may be tabular, individual cells or named ranges, and so on, and through a chain of operations you end up with a result, or many results. Change one input and it immediately flows through.
Does that analogy hold water?
4
I’ll take that you are talking about the stateless characteristic of pure functional language.
You take the initial state as an input, you return the final state as an output. Nothing fundamentally different from what you do in a language with a notion of state, excepted that you are more explicit about it and that can be tedious and less efficient(*).
Note that you don’t necessarily have to modify all places which reference your object: they may hold a token and then you just have to modify the data structure which map the tokens to their current state. By then you are implementing a state-full system using a stateless language and you get back the issues of state-full languages.
(*) For instance I’ve looked for — and not found — the data structure which allow me to return in O(1) a modified copy of a data structure indexable by consecutive integers in O(1) as well.
4
In short, each state of an entity is its own entity. So in your classic “ordering” business logic, there’s an Order
entity, and an OrderVersion
entity. Both are immutable. Logic that adds a line item takes the old version and a new OrderLineItemVersion
as an input and returns a new OrderVersion
entity.
It makes some things easier (particularly “undo” functionality), but some stuff is more difficult.