Ci sono enormi benefici per le funzioni pure nella programmazione funzionale, ma gli stessi vantaggi possono essere ottenuti nella programmazione imperativa con un uso intenso del modello di servizio?
Chiedo perché voglio trovare un modo per mappare le idee dalla pura programmazione funzionale alla programmazione imperativa concentrandomi sull'uso dello spazio dello stack o dello spazio dell'heap immortale. (I.E. Un focus su hard real time)
Il modello di servizio di cui parlo è lo stesso di qui: link
E con un uso intensivo, intendo che una funzione esegue solo effetti con l'effetto fornito - funzioni complete passate nei suoi argomenti.
Esempio (in C ++):
template <typename SetPixel>
void mandelbrot(float x1, float y1, float stepX, float stepY, int resX, int resY, SetPixel setPixel) {
float y = y1;
for (int i = 0; i < resY; ++i, y += stepY) {
float x = x1;
for (int j = 0; j < resX; ++j, x += stepX) {
float za = 0;
float zb = 0;
float zm = 0;
float ca = x;
float cb = y;
int k = 0;
while (zm < 4.0f) {
float za2 = za * za - zb * zb + ca;
float zb2 = 2 * za * zb + cb;
float zm2 = za2 * za2 + zb2 * zb2;
++k;
if (k > 255) {
k = 0;
break;
}
za = za2;
zb = zb2;
zm = zm2;
}
setPixel(j, i, k);
}
}
}
Il metodo precedente mandelbrot
può essere utilizzato sia in modo puro che impuro. Posso fornire un SetPixel che raccoglie i risultati e produce un array immutabile (rendendo gli effetti completamente locali e quindi un puro calcolo). Oppure posso anche passare un SetPixel pieno di effetti ed eseguire direttamente gli effetti rendendolo un calcolo impuro.
Questo mi consente di eseguire il debug di funzioni in modo puro o di eseguire direttamente effetti in runtime in questo modo:
mandelbrot(
-2.0f,
-2.0f * 480.0 / 640.0,
4.0f / 640.0,
4.0f / 640.0,
640,
480,
[renderer](int x, int y, int c) {
SDL_SetRenderDrawColor(renderer, (c*5) & 0xFF, 0, 0, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
);
Ho perso i benefici delle funzioni pure adottando questo approccio?
Un secondo tentativo:
Come i commenti seguenti e la risposta di Derek Elkins suggeriscono che la risposta è "No". Ho perso i benefici delle funzioni pure.
Quindi ecco un secondo tentativo. Usa semantica di movimento C ++ + unique_ptr per ottenere un sistema di tipo lineare, e quindi lo usa per implementare un sistema di effetti in uno stile di manipolazione allo stato puro. Questo è simile a come Mercury fa IO.
struct DoNotDelete {
void operator()(void *ptr) {}
};
struct Void {};
typedef unique_ptr<Void,DoNotDelete> RealWorld;
template <typename SetPixel, typename S>
S mandelbrot(S state0, SetPixel setPixel, float x1, float y1, float stepX, float stepY, int resX, int resY) {
S state = move(state0);
float y = y1;
for (int i = 0; i < resY; ++i, y += stepY) {
float x = x1;
for (int j = 0; j < resX; ++j, x += stepX) {
float za = 0;
float zb = 0;
float zm = 0;
float ca = x;
float cb = y;
int k = 0;
while (zm < 4.0f) {
float za2 = za * za - zb * zb + ca;
float zb2 = 2 * za * zb + cb;
float zm2 = za2 * za2 + zb2 * zb2;
++k;
if (k > 255) {
k = 0;
break;
}
za = za2;
zb = zb2;
zm = zm2;
}
state = move(setPixel(move(state), j, i, k));
}
}
return state;
}
template <typename SetPixel>
RealWorld interpreter(RealWorld realWorld, SetPixel setPixel) {
return move(mandelbrot(
move(realWorld),
setPixel,
-2.0f,
-2.0f * 480.0 / 640.0,
4.0f / 640.0,
4.0f / 640.0,
640,
480
));
}
Quindi per dargli il via, A RealWorld
è costruito da qualche parte in main()
, in questo modo:
Void realWorldState;
RealWorld realWorld = unique_ptr<Void,DoNotDelete>(&realWorldState);
RealWorld realWorld2 = interpreter(
move(realWorld),
[renderer](RealWorld realWorld, int x, int y, int c) {
SDL_SetRenderDrawColor(renderer, (c*5) & 0xFF, 0, 0, 255);
SDL_RenderDrawPoint(renderer, x, y);
return realWorld;
}
);
Questo mi consente di utilizzare un valore immutabile puro (come una mappa ad albero immutabile di posizioni di pixel in colori) o il Real World stesso.