2

I have a struct called State which holds all the variables for the program. Rather than being modified by functions directly, it is the value returned. Here is some code:

#define USERNAME_LENGTH 20
#define MAX_NUMBEROFUSERS 64
#include <string.h>
#include <stdio.h>

struct User {
    char name[USERNAME_LENGTH +1];
    int id;
};

struct State {
    int numberOfUsers;
    struct User users[MAX_NUMBEROFUSERS];
};

// create a new instance of state, called "_";
struct State _;

// function for returning the state with a new user in it
struct State newUser() {

    struct State state = _;

    char name[USERNAME_LENGTH +1];
    sprintf(name, "test username #%d", state.numberOfUsers);

    struct User newUser;
    newUser.id = state.numberOfUsers;
    memset(newUser.name, '\0', USERNAME_LENGTH +1);
    memcpy(newUser.name, name, USERNAME_LENGTH);

    state.users[state.numberOfUsers] = newUser;
    state.numberOfUsers++;

    return state;
}

int main() {

    while (_.numberOfUsers < 10) {
        _ = newUser();
    }

    printf("Users are:\n");
    for (int i=0; i<_.numberOfUsers; i++) {
        printf("%s\n", _.users[i].name);
    }
    printf("%d users total.\n", _.numberOfUsers);

    return 0;
}

Output:

Users are:
test username #0
test username #1
test username #2
test username #3
test username #4
test username #5
test username #6
test username #7
test username #8
test username #9
10 users total.

As you can see, the entire state is passed around until the program terminates. The newUser() function copies the state to a local variable which is modified and then returned. I chose not to pass the state as an argument to the function, because I wanted to be able pass other arguments if needed such as:

newUser(const char* username) {...}

Question: I think this is pretty cool, but is it practical? Can the compiler (GCC) optimize for this sort of almost-functional style?

I can see the benefit of explicitly keeping track of program state, but is it worth the trouble?

  • you can pass multiple arguments to a function you know, struct State newUser(const struct State* , const char* username){...} – ratchet freak Sep 8 '16 at 14:47
5

I can see why this is tempting, but to me it looks like a pretty good example of the Anti-pattern known as a God Object.

For a small enough program your global _ object would contain just a few members that are directly related to one another.

If your program were much bigger, it's unlikely that this would be the case. You might have a dozen members to support your help system, another half dozen dedicated to configuration, more dedicated to various forms of I/O, and so on. The only thing these members would have in common would be inclusion in this global object (or struct - makes no difference).

Furthermore, your application would become brittle - changing the type of one member would require you check the entire code base to see what would be affected.

Testing might also become "interesting" - any method/function anywhere can in theory change any part of the global state at any time.

  • This sounds exactly like the sort of thing I'm trying to avoid! Thank you. – ridthyself Sep 8 '16 at 14:32
  • 1
    This is why I've always found the notion of a World object in functional programming bizarre. It's not enough that you have a single World object... you actually have to make it immutable and replace it with another World object every time something changes. I guess that's why there's a difference between theory and real-world practice. – Robert Harvey Sep 8 '16 at 17:13
  • 3
    @RobertHarvey Functional programming is very practical and performant. The naive implementation of simply copying or recreating the entire World object is not what's actually done. – Ryan Reich Sep 8 '16 at 17:55
  • 1
    There was the possibility that you were saying that in theory, functional programming is possible, but in the real world, it is inefficient. If not, I apologize for misunderstanding. In any case, I had to object for the public record. – Ryan Reich Sep 8 '16 at 18:03
  • 3
    I think it's a bad fit for the language because in a real functional language, there is compiler support for state objects that makes it easy to avoid exactly what you are finding necessary: copying everything over on any change. Because C is imperative, doing what is necessary to make it functional is simply awkward and un-idiomatic. Do what the language makes easy, and if you want to write a functional program, use Haskell. – Ryan Reich Sep 9 '16 at 14:30
3

Given that you say you want to emulate functional style I would expect you to pass the current state in as a parameter:

struct State newUser(const struct State& _) {
    struct State state = _;

    //...

    return state;
}

Having this lets you create smaller sub state structs and only pass functions the state they care about.

However Each function call requires a copy of the entire state object. This results in a lot of copies of the state as modifications migrate up and down the stack. Dynamically allocating and sharing parts of the state that don't change often will help with that but will complicate the code with reference counting.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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