3

As a university student who just have been learning programming for a year. After I learned about the concept of state machine and the pure function in functional programming, I suddenly got an idea about combining them together as a programming style.

  • Everything is either a state machine or a function. (So all the variables and objects in the Object Oriented Programming are considered as state machines.)
  • If a function is a member function of a class. Then it is an action which will change the state of the instance of this class. (As mentioned above, this instance object has been considered as a state machine)
  • If a function is not a member function of a class. Then it is a pure function.
  • An action can only change the state of a single state machine. If the actions of multiple state machines need to be execute together. Then these changes should be abstracted as an action of a single higher level state machine which represent all these states machines.

After the exciting of eureka, I suppose I couldn't be the first one who got this idea. There must be some research about this programming style and I'd like to learn. But I really didn't find any information about this kind of programming style which specifically summarize principles just like I summarized above (The search result is filled with introductions to state machine and pure function). Does anyone know anything similar?

For more details, here is the sample C++ code about my idea.

#include <stdlib.h>

namespace Array
{   /* Although the pure function can't be a member function of class,
     * I still need a way to organize the data structure and the
     * functions together. I choose to use namespace to do this. */
    class Object {
    public:
        int length;
        int *array_data;
        Object(int l) {
            length = l;
            array_data = (int*)calloc(length, sizeof(int));
        }
        ~Object() {
            free(array_data);
        }
        // It's not a pure function for it change the state of array.
        void set(int index, int data) {
            array_data[index] = data;
        }
    };
    // It's a pure function.
    int get(Object *array, int index) {
        return array->array_data[index];
    }
    // It's another pure function which use a buffer to return
    // the result.  It only write the result to the buffer and
    // doesn't change anything else.
    void get_all(int* buffer, Object *array) {
        int i;
        for (i=0; i<array->length; i++) {
            buffer[i] = array->array_data[i];
        }
    }
}

int main() {
    Array::Object test_array(10);
    test_array.set(0, 10);
    for (int i=0; i<10; i++) {
        test_array.set(i, i);
    }
    int value = Array::get(&test_array, 7);
    int buffer[10];
    Array::get_all(buffer, &test_array);
}

I suppose the biggest advantage of this programming style is readability. I used to write addon of Blender to practice programming. And I still remember when I read the source code of some other addons of Blender, I was really confused by whether a variable is changed or not. As for my programming experience, I found that the main cause of most bugs is the unexpected state change of variables and objects.

This programming style will easily remind us about whether the state is changed or not. When you see a function which is not a member function of class, you can confirm that this function is a pure function and didn't change anything. It also owns the advantages of pure function that it's easy to analyze the code and locate the bugs.

4 Answers 4

5

... really confused by whether a variable is changed or not ...

... main cause of most bugs is the unexpected state change of variables and objects ...

The idiomatic way to handle this in C++ is const correctness. Don't pass non-const references to objects unless they're going to be mutated. Pass const references wherever possible. Const-qualify methods where possible.

This effectively partitions your code into definitely-not-mutating and possibly-mutating areas. And, unlike pure functions, it has first-class language support in the const keyword.


Note also that your stateful objects are not necessarily state machines. Can you enumerate the states of your Object? Are there state transitions? State machines are automata, not just bags of data.


Now, there are some instructive issues in your code - I'm just going to lump them all together:

#include <stdlib.h>

This is the C header. In C++ it should be:

#include <cstdlib>

namespace Array
{
    class Object {
    public:

Making data members public is a very bad way to control when they get mutated! Leave them private instead to limit which functions can change them.

        int length;
        int *array_data;

prefer std::unique_ptr<int[]> (or just std::vector) to manual memory management

        explicit Object(int l) {

make single-argument constructors explicit unless you really want implicit conversions from int to Object

            length = l;
            array_data = (int*)calloc(length, sizeof(int));
        }
        ~Object() {
            free(array_data);
        }

        void set(int index, int data) {

In idiomatic C++ we already know this is non-const (impure) ... because it isn't const

            array_data[index] = data;
        }

Next rewrite the "pure" free function as a const method. Then you can make the data members private, which is a much bigger win for predictability than any convention.

You could also make this a friend function taking an Object const& parameter. Either way you get compiler support instead of a convention you have to enforce manually.

        int get(int index) const {
            return array_data[index];
        }
    };

    void get_all(int* buffer, Object *array) {
        int i;
        for (i=0; i<array->length; i++) {
            buffer[i] = array->array_data[i];
        }
    }

There's a severe problem with the get_all function. It's supposed to be pure, but with public data members there's nothing to prevent the following code:

   Object a(5);
   Object b(10);
   get_all(a.array_data, &b);

which is both non-pure (in your terms) because it mutates a, and completely broken because there is no bounds checking on the target.

3
  • You're right. I just realized that it's not exactly the state machine. I want to emphasize that the state is mutable and can be changed by action. So I choose the term state machine to describe it without thinking twice. There are infinite states for an array.
    – Equescript
    Oct 7, 2022 at 3:26
  • I wrote the sample code just to show my idea so I ignored many possible issues. The point is that I try to find a way to distinguish the pure function and none pure function. Using the const keyword is one way to do this and that should be enough for most of time. But is there a programming style choose to use wheather it is a member function of a class to do this? And if no, is that possible?
    – Equescript
    Oct 7, 2022 at 3:38
  • If your main concern is state being silently changed when you don't expect it, then encapsulation should be your main concern. Make data members private and clearly distinguish between non-const mutating methods and const non-mutating methods.
    – Useless
    Oct 7, 2022 at 10:12
1

An action can only change the state of a single state machine. If the actions of multiple state machines need to be execute together. Then these changes should be abstracted as an action of a single higher level state machine which represent all these states machines.

This part sounds a bit like the "clean code" notion that a function should do one thing, or perhaps more generally the single-responsibility principle, which is definitely a popular notion and is the "S" part of the SOLID principles.

As for state machines specifically, I've definitely used state machines composed of other state machines for control systems, in particular for example where two otherwise decoupled state machines need to be coordinated in a particular sequence for a given operation. This technique works best when the number of (valid) high level states is smaller than the product of all low-level states (similarly for transitions).

That's generally just called "composition", or maybe "composition over inheritance" as a way to share implementation/state information that's not inheritance; I'm not aware of a specific name for it regarding state machines as opposed to objects generally.

This programming style will easily remind us about whether the state is changed or not.

There is already a keyword in c++ for this, and as f222 and Useless already pointed out in their answers, it is "const".

Some reasons why the use of a language keyword is superior to convention/style guide are, for example:

  1. the compiler will enforce your constraint, so you get an error at compile time instead of (potentially much later) at runtime if the constraint is not respected.
  2. because the constraint is in the source code near where it is used, any programmer looking at the code can see your intent right there, without having to also know your styleguide (the code "self-documents" this constraint)
  3. because the constraint is in the source code near where it is used, the constraint is less likely to be missed during reviews when something changes, whether it is the style guide / convention, or the implementation of the code, that is being changed.

Generally, whenever a language keyword or, if available, attribute does what you want, you should always make use of them instead of relying only on convention (though you can use both if you like).

Useless's answer also pointed out the issue of public data members. Using "private" lets the compiler further enforce your convention around access, and we already established why having the compiler enforce your conventions is better than leaving them up to style. Note that you can still use free functions to access private data members with the "friend" keyword if necessary.

But once you're using const, private, and friend, I'm not sure what remaining benefit there is to separating all of what would be const members into free functions, with all of the non-const members remaining members. The problems that your style intended to prevent, are now prevented for you in part by the compiler, and in part by your source being self-documenting, regardless of whether the function is free or member. I am reminded of the koan about objects and closures; sometimes a thing is just another thing from a different point of view, and it doesn't really make much difference which one you have.

You might, however, consider making the split between free and member function somewhere else. Specifically, based not on const-ness, but rather on the need to know the implementation details (aka the need to know about and access the private data). Functions that truly need to access the private members directly would be member functions, and the other functions would be free functions implemented on top of those member functions. As Herb Sutter shows while reviewing std::string, this technique can hide implementation, avoid monolithic objects, and make it simple to extend functionality (the open-closed principle, aka the "O" part of SOLID). I'm not aware of him addressing your specific "free functions are purely functional, member functions are not" design, but the kind of decomposed/encapsulated design he proposes may help avoid bugs and ease understanding like your approach also intended.

Everything is either a state machine or a function.

State machines are most useful when there are a smallish number of states with well-defined, limited transitions between the states, so they are easy to reason about and understand. This makes them handy for example to run simple control systems, where the desired behavior of the system has a natural mapping to a finite state machine, whether implemented in hardware or software.

If we relax the restrictions on "smallish" and "limited" to allow objects with any data changed by member functions, it may not be literally "wrong", because discrete binary data has a finite number of possible values, a class's data members are its state, and though a canonical state machine would receive all its inputs periodically and handle any transitions at the same time, it could also have input changes indicated by method calls. However, it's not very helpful, because it would effectively mean that all objects are state machines. But, we we already have a word for objects, which is "objects", and and now we don't have a word for the narrower definition of "state machine", because we're using "state machine" to refer to objects instead.

If you mean to say "everything is an object or a function", that's pretty much what c++ has to say about objects already, though c++ also notes that types, templates, references, etc. exist. That's not unique to c++, though, and programming in this manner can be described as "object-oriented", even if you never use inheritance.

1
  • Thanks for your answer! I suppose using keywords is always better than invent some strange style. Looks like the const keyword is what I really need in C++. And using a programming style to distinguish the pure function and other functions is inefficient.
    – Equescript
    Oct 7, 2022 at 4:02
0

This could be useful a coding convention, but I think the same could be accomplished with a simple naming convention for the class methods:

  • test_array.set() // It's not a pure function for it change the state of array.
  • test_array.get_pure() // It's a pure function.
  • test_array.get_all_pure() // It's another pure function...

Both methods have the downside that the convention can say a function should be pure, but in reality the function is not pure. (Or a function starts pure, then side effects are added later.)


Also, usually a "pure function" is defined as a function that has no side-effects (interactions with external mutable state). That means: a pure function should result in the same outputs given the same inputs, no matter how many times you call the function, or when you call the function.

I would argue get_all() is not a pure function because both buffer and *array are both external mutable state. get_all() is called because the side effects are desired; not for the return value (which is always void). Pure functions are generally called to get a value they calculate.

Finally, I would say combining (mutable) state machines and pure functional programming is not a common style because the power of pure functions is reduced as soon as you add mutation. It's more useful to know that a variable is immutable (it always had the same value) than knowing that a function is pure and won't change the value of a variable.

However, I found this question looking for pure, immutable state machines. Like this: https://brucou.github.io/documentation/v1/api/createPureStateMachine.html

-1

No offense but for me this code looks really bad.

First I can see some non-CPP way of doing things (raw pointers, calloc) but I won't talk about those because they are not architectural stuff.

In an architectural point: First sending the array as a parameter is replacing the this (why add a parameter when you can have it hidden).

Secondly by sending the array as a parameter you are using a pointer (which means it could be null) and you don't check for that (in C++ you should use a const reference instead).

Finally in C++ you can (and should when necessary) mark your methods as const:

class array
{
    int get(unsigned int index) const
    {
        return array_data[index];
    }
}

Adding the const after the function name tells that it will not modify the class.

I think it is more a lack of C++ knowledge that makes you think it looks right.

1
  • I'm sorry about my code because I used to use python and I'm not familiar with C++. I just want to use the namespace to organize the data structure and the function. In fact, I try to show that using "wheather it's a member function of class" to distinguish the pure function and other function and I wonder is that really feasible?
    – Equescript
    Oct 7, 2022 at 3:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.