Programmazione funzionale simulata in C - passando l'intero stato del programma come argomento di funzione

2

Ho una struttura chiamata State che contiene tutte le variabili per il programma. Invece di essere modificato direttamente dalle funzioni, è il valore restituito. Ecco alcuni codici:

#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, '
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.
', 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:

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

Come puoi vedere, l'intero stato viene trasmesso finché il programma non termina. La funzione newUser () copia lo stato in una variabile locale che viene modificata e quindi restituita. Ho scelto di non passare lo stato come argomento alla funzione, perché volevo essere in grado di passare altri argomenti, se necessario, come ad esempio:

#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, '
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.
', 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; }

Domanda: Penso che sia abbastanza interessante, ma è pratico? Il compilatore (GCC) può ottimizzare per questo tipo di stile quasi funzionale?

Riesco a vedere il vantaggio di tenere esplicitamente traccia dello stato del programma, ma ne vale la pena?

    
posta ridthyself 08.09.2016 - 15:32
fonte

2 risposte

5

Posso capire perché questo è allettante, ma per me sembra un buon esempio di Anti-pattern noto come Oggetto di Dio .

Per un programma abbastanza piccolo il tuo oggetto _ globale conterrà solo alcuni membri direttamente correlati tra loro.

Se il tuo programma fosse molto più grande, è improbabile che questo sia il caso. Potresti avere una dozzina di membri per supportare il tuo sistema di assistenza, un'altra mezza dozzina dedicata alla configurazione, più dedicata a varie forme di I / O e così via. L'unica cosa che questi membri avrebbero in comune sarebbe l'inclusione in questo oggetto globale (o struct - non fa differenza).

Inoltre, la tua applicazione diventerebbe fragile: la modifica del tipo di un membro richiederebbe di controllare l'intero codice base per vedere quali sarebbero interessati.

I test potrebbero anche diventare "interessanti" - qualsiasi metodo / funzione può in teoria modificare in qualsiasi momento qualsiasi parte dello stato globale.

    
risposta data 08.09.2016 - 16:20
fonte
3

Dato che dici di voler emulare lo stile funzionale, mi aspetto che tu passi lo stato corrente come parametro:

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

    //...

    return state;
}

Avere questo ti permette di creare strutture secondarie più piccole e di passare solo le funzioni allo stato che gli interessa.

Tuttavia, ogni chiamata di funzione richiede una copia dell'intero oggetto di stato. Ciò comporta molte copie dello stato mentre le modifiche migrano su e giù nello stack. Assegnare e condividere in modo dinamico parti dello stato che non cambiano spesso aiuterà in questo, ma complicherà il codice con il conteggio dei riferimenti.

    
risposta data 08.09.2016 - 16:53
fonte

Leggi altre domande sui tag