OK, so the title is a little clickbaity but seriously I've been on a tell, don't ask kick for a while. I like how it encourages methods to be used as messages in true object-oriented fashion. But this has a nagging problem that has been rattling about in my head.
I have come to suspect that well-written code can follow OO principles and functional principles at the same time. I'm trying to reconcile these ideas and the big sticking point that I've landed on is
A pure function has two qualities:
Calling it repeatedly with the same inputs always gives the same result. This implies that it is immutable. Its state is set only once.
It produces no side effects. The only change caused by calling it is producing the result.
So, how does one go about being purely functional if you've sworn off using
return as your way of communicating results?
The tell, don't ask idea works by using what some would consider a side effect. When I deal with an object I don't ask it about its internal state. I tell it what I need to be done and it uses its internal state to figure out what to do with what I've told it to do. Once I tell it I don't ask what it did. I just expect it to have done something about what it was told to do.
I think of Tell, Don't Ask as more than just a different name for encapsulation. When I use
return I have no idea what called me. I can't speak it's protocol, I have to force it to deal with my protocol. Which in many cases gets expressed as the internal state. Even if what is exposed isn't exactly state it's usually just some calculation performed on state and input args. Having an interface to respond through affords the chance to massage the results into something more meaningful than internal state or calculations. That is message passing. See this example.
Way back in the day, when disk drives actually had disks in them and a thumb drive was what you did in the car when the wheel was too cold to touch with your fingers, I was taught how annoying people consider functions that have out parameters.
void swap(int *first, int *second) seemed so handy but we were encouraged to write functions that returned the results. So I took this to heart on faith and started following it.
But now I see people building architectures where objects let how they were constructed control where they send their results. Here's an example implementation. Injecting the output port object seems a bit like the out parameter idea all over again. But that's how tell-don't-ask objects tell other objects what they've done.
When I first learned about side effects I thought of it like the output parameter. We were being told not to surprise people by having some of the work happen in a surprising way, that is, by not following the
return result convention. Now sure, I know there's a pile of parallel asynchronous threading issues that side effects muck about with but return is really just a convention that has you leave the result pushed on the stack so whatever called you can pop it off later. That's all it really is.
What I'm really trying to ask:
return the only way to avoid all that side effect misery and get thread safety without locks, etc. Or can I follow tell, don't ask in a purely functional way?