4

I have read from various sources as a state being defined as something along the lines of:

  • 'the program's condition regarding stored inputs';
  • 'contents of memory locations at any given time in the program's execution'

But then I look up what the characteristic of being stateless is (e.g "Haskell is stateless"):

  • 'when an application isn't dependent on its state';
  • 'physical state can't be changed'
  • 'Same output for same input - address in memory always stays the same'
  • 'methods don't depend on an instance and its corresponding instance variables'

Now, I must have misunderstood the (vague?) former definition, as surely FP languages which go hand-in-hand with the 'stateless' model stores inputs too?? Or is this something about functions simply being evaluating rather than mutating data?


Well, I sort of get that such models are sometimes powerful - after reading about its use in program verification, debugging and concurrency.

But it did get fairly complicated when I then read on about how:

  • "it eliminates a whole class of multithreading bugs related to race conditions"
  • more expressive code (whatever that is?)
  • "static evaluation ... can be used to favourably guide computer's positions [in a tic-tac-toe game tree]" https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf (or perhaps static typing is for another question entirely?)

So I also wondered about the advantages of being able to manipulate states, in iterative programming languages, and many forums gave examples like how altering one's 'age' via calling add() will mutate the 'age' variable outside of its scope.

Maybe it's my lack of experience with OOP, but what are the exact advantages of using states in wider applications?

If any example code could be given, please could you try and stick to Python/Haskell as the contrasting representatives of these opposite disciplines? My inexpertise in reading other languages does seem to hinder my understanding of other posts' explanations.

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
  • 1
    Do note that FP isn't all about having states or not; it's in JS but do read have a look at https://softwareengineering.stackexchange.com/questions/258114/help-me-to-understand-stateless-software-in-functional-programming – mikerover Dec 13 '19 at 22:54
  • 1
    All but the very simplest of programs have state. That's required for basically everything. Functional programming doesn't say to get rid of state. It says 1) make access to state explicit, and 2) make state values immutable so that sharing them doesn't cause problems. – Carl Dec 13 '19 at 23:44
  • This is not your original question but I guess you should review your [definition of stateful computations](https://stackoverflow.com/a/3722218/5536315) –  Dec 14 '19 at 09:22

1 Answers1

0

If you consider a program where there is no state whatsoever, and the program is not able to read external states (for example, checking the system time), that would entail that any function, besides what values may be passed as parameters, would have no way of differentiating any different circumstances because no different circumstances can exist.

With this class of program, the run-time is effectively redundant as an optimizing compiler could derive any resulting outputs statically. Its essentially equivalent to writing out a large math equation. You can solve it, but the idea of "running the program" is redundant because it has an output inherent to the program itself that cannot change.

Obviously this doesn't resemble most programs, even ones that may be described as "stateless". Usually there is some minimal kind of state, such as input initially passed to the program. For example, imagine a program that outputs the first N digits of the square root of a number K. This program now has an initial state, but beyond knowing what N and K are, the program doesn't need to track program-level states like what day of the week it is, whether or not the user prefers MDY vs DMY date format, etc. However, since the program almost certainly involves some kind of dynamic looping to find N digits, it would need to have some kind of state associated with the loop (for example an iteration number)

So when code is referred to as "stateless" its not a 100% promise, but a kind of qualifier of the degree to which the code depends on state.

So what are the advantages and disadvantages here? The more your code depends on state, the more likely it is to do something that the programmer wasn't expecting. Remember, state is something inherent to the runtime. But we don't write code at runtime. We can try to imagine all the different possible runtime states that could happen, but this quickly gets out of hand.

Examples

Here's an example of the same python program in a stateful vs stateless way.

stateful.py

import math

angle_type = 'radians'

def cosecant(x):
    if angle_type == 'radians':
        return 1/math.sin(x)
    elif angle_type == 'degrees':
        return 1/math.sin(x*math.pi/180)
    else:
        raise NotImplementedError('cosecant is not implemented for angle type', angle_type)
================== RESTART: F:/Documents/Python/stateful.py ==================
>>> cosecant(1)
1.1883951057781212
>>> angle_type = 'degrees'
>>> cosecant(1)
57.298688498550185
>>> angle_type = 'turns'
>>> cosecant(1)
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    cosecant(1)
  File "F:/Documents/Python/stateful.py", line 11, in cosecant
    raise NotImplementedError('cosecant is not implemented for angle type', angle_type)
NotImplementedError: ('cosecant is not implemented for angle type', 'turns')
>>> 

stateless.py

import math

def cosecant(x):
    return 1/math.sin(x)

def cosecant_degrees(x):
    return 1/math.sin(x*math.pi/180)
================= RESTART: F:/Documents/Python/stateless.py =================
>>> cosecant(1)
1.1883951057781212
>>> cosecant_degrees(1)
57.298688498550185
>>> cosecant_turns(1)
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    cosecant_turns(1)
NameError: name 'cosecant_turns' is not defined
>>> 

This example may seem odd, but the same principals and pitfalls extend to more complex programs. We can see how using state here can lead to problems. Consider stateful.py. One problem is that the person calling cosecant doesn't necessarily know the current state of angle_type, they would have to set it every time before using cosecant to make sure that it will do what the want it to do. Since programmers tend to not like repeating themselves, someone may declare mymathlib.angle_type = 'degrees' at the start of their program, and assume nothing else will ever change it. For example, what if you are working in degrees, but then you call a subroutine that changes angle_type to 'radians' and doesn't change to degrees upon finishing.

If other parts of the program are changing it, then the programmer effectively has to set the value to what the want every time before calling it. And even still, if code is running on multiple threads, there is no guarantee that when you set angle_type to 'degrees', another thread didn't immediately set it to something else before your cosecant call gets executed (a race condition).

In our stateless version of the program, all of these problems disappear. What's the cost? Well now we have 2 different functions instead of 1. Why is this a cost? Well, generally, it is considered good practice to keep an API smaller rather than larger, having multiple tools that do the same thing is confusing for people trying to use your library. Because of this principle, it is sometimes tempting to smoosh 2 or more different functions into 1 if they seem to be doing the same thing. In general this isn't terrible, in fact it often helps a library in terms of maintenance and usability. In this case though, it has caused more harm than good because in the stateful program, while the programmer can effectively use it to do the same thing, we no longer have the simple guarantee that the function will do what you think it will do every time.

This may on the surface seem like a silly example, but consider that python's decimal module does nearly the same thing. In the decimal module, states variables including the number of decimal places of precision and rounding rules are stored in the current thread's context. This isn't quite as problematic as the previous example. Since each thread has its own context, we don't have to worry about the race condition, but there are still potential trouble spots, like if a subroutine changes state without nicely changing it back for you.

It's easy to rationalize this kind of design and say "a competent programmer should be able to analyze the code and be able to avoid these kind of state related problems if they take the time to think about it". This is true in theory, but if we look at how people actually write code, the principle of least effort trumps everything else most of the time. Look at examples of use of the decimal module in the official documentation, or any tutorial, and you will notice a common pattern. The references to decimal.getcontext() outnumber the references to decimal.setcontext() 10 to 1 -- that is if setcontext is mentioned at all, often it is not. In order to manage state in a competent "safety first" kind of way, both of these tools are equally important and if anything decimal.setcontext() should be used more often since it is what you use to guarantee consistent behavior. Because of this least-effort principle, it is inevitable that programmers, especially beginners, will write code like this that may work in testing, but has no hard guarantee of future safety as the program evolves.

Conclusion

So is stateful code evil, and stateless code is our savior? Well maybe. It could help to think of this little epigram as a guiding principle to avoid certain pitfalls. The reality is that often stateful code is hard to avoid, or even inherent to the program's design. For example, how could we make a video game without state? How do we make a health counter? How does the player move around the level and track its position? Can we have scores or win conditions? Stateful code is not going anywhere, but it does genuinely help to have an understanding of the common pitfalls of designing programs in that way.

Hymns For Disco
  • 7,530
  • 2
  • 17
  • 33