Concurrency - The difference between functional call context & object context

December 17, 2007 at 10:25 PM | categories: python, oldblog | View Comments

A common idiom in functional programming at least with the functional languages I've used and systems I've needed to build - is a call context. That is any sufficiently large functional piece of code is at some level modelling interacting with a model. So in pseudo-functional-language you (conceptually) end up doing something like:
fun move_x context distance:
       x,y,z = Parts(context)
       return Context(x+distance,y,z)
This context then gets passed around, and updated. This means you can end up with lots of functions that look like this:
fun move_x contet ...
fun move_y context ...
fun move_z context ...

This looks very similar to something you get in python:
def move_x(self, ...
def move_y(self, ...
def move_z(self, ...

However there's a big difference. In the object oriented python (or almost any other OO language really), the value of "self" is a persistent value (ie exists while someone has a reference to it), and "self" is itself mutable.

This has some implications that you don't have in a functional world for concurrency. For example, you could model and pass on an updated  indication something is locked something like this:
fun ..with_lock context ...:
       x,y,z,_ = Parts(context)
       return Context(x,y,z,"locked")
ie simply update our functional object to say that the state is locked. That sounds remarkably similar to doing something like this:
def ..with_lock(self, ...
     self.locked = True
However, remember the difference here. In the functional version the Context is (logically at least) creating a completely new value, with the exception of the fact that one attribute of the structure is different. However the OO version is mutating a piece of storage.
This is perhaps the simplest example I can see of how a functional style is easier to work with in a concurrent world with shared data, and perhaps also what aspect of shared data is dangerous.

You see, because the functional style essentially takes a value and copies it before mutating it, it naturally means that the only bits of code that see the copy are the pieces of code called next. eg:
def hello(context, count):
    if count >1:
        return hello("hello"+context, count -1)

In this example the context just bigger and bigger and bigger. It is however never shared - except explicitly. Just because on the surface it looks similar, it's nothing like it. The coding and design principles look similar, but they're not. The fact is it is object oriented, but it's a  functional object and it is only visible within the thread that created it. If someone else has another object that looks the same, that's nice, but it isn't the same.

(All of this goes back to fundamentals of functional languages)

However, if I create an object in an OO language, I can easily create a collection of threads that can mutate that object. As soon as I do, in an object oriented language, without hackery what one thread of control can see about the object, all threads of control can see about the object. That's right - all attributes become instantly shared data.

This means that suppose I acquire a lock, and wish to pass this onto another function I'm calling. In a functional language, I can merely update the value in the (thread-of-control-private) context, whereas in the OO language, I can't update the context. I have to create a new functional context to pass around if I wish to achieve something similar. ie I have to do this:
def meth(self, callcontext, ...)
Where callcontext contains the new indication saying we're locked.

As a result, this leads me to ponder:
The core problem here, as ever, in the OO version isn't just shared state - it's shared mutable state
More than that it's also the fact that the mutable state can't be easily used to control thread local state by default.

Doesn't that open the door to something more halfway? Add support for thread private object values? I guess this is what Unified Functions and Objects - UFO (which I use many years ago) - did - I just never appreciated what it was really doing. (Or if it didn't, what it should've done :-)

I think what this also suggests (to me) is that thread local storage actually needs promoting to a much higher level - to the extent that updates to objects default to updating attributes in thread local storage rather than direct unmanaged update - with the idea being that the managed updates can then be managed in a somewhat better manner.

Hm. I suppose what that is saying is that the mutable shared object state should default to being software transactional memory, whereas the version you normally access is thread local, since then you would (hopefully) get the best of both worlds...

Hm, shared self.__dict__ as an STM , and threads accessing it only doing so through special dict proxies which get commited ? hmm.....

blog comments powered by Disqus