L'utilizzo intensivo del modello di servizio sostituisce le funzioni pure senza perdere i vantaggi?

4

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.

    
posta clinux 28.11.2016 - 09:37
fonte

1 risposta

2

Come menziona l'articolo di riferimento, questo è molto simile a imitare alcuni OOP di base in Haskell, anche se, come menzionato, fornisce una composizione di prima classe. È anche strettamente correlato all'iniezione di dipendenza come viene comunemente praticata in OOP. In ogni caso, un modo per pensare e modellare ciò che sta accadendo è attraverso il concetto di capacità degli oggetti .

In un linguaggio di capacità degli oggetti, l'autorità di fare qualcosa viene catturata dalla nozione di capacità. Solitamente si tratta di cose specifiche relative all'I / O, ma può essere più astratto. L'idea chiave è che l'unico modo per ottenere una capacità è se qualcuno te lo concede. Per creare un linguaggio che imponga quella proprietà, hai bisogno di due cose: l'incapsulamento come se fossi abituato nella maggior parte dei linguaggi OO e la rimozione di tutte le autorizzazioni ambientali . In parole povere, ciò significa per un linguaggio come Java, senza classi / funzioni "di primo livello" che hanno autorità e nessuna variabile mutabile globale, ad es. proprietà statiche mutevoli (in senso Java / C #) di una classe. La maggior parte dei linguaggi OO fornisce l'incapsulamento necessario (sebbene non C ++), ma pochi eliminano l'autorità dell'ambiente. Rimozione costringe uno stile di dipendenza-iniezione-come. Questo è abbastanza chiaramente illustrato nella lingua Newspeak .

I vantaggi menzionati sono i vantaggi comuni dei linguaggi di capacità degli oggetti e dell'iniezione delle dipendenze. Infatti, nei linguaggi di capacità degli oggetti, la capacità di avvolgere o deridere qualsiasi oggetto che trasporta autorità è alla base delle funzionalità di sandboxing e revocabile. Detto questo, tutto questo è stato un po 'un non-sequitur per quanto riguarda la tua domanda.

La risposta alla tua domanda è fondamentalmente "no". I vantaggi che menzioni non sono benefici del codice puramente funzionale ma i vantaggi di rendere esplicite le tue dipendenze. I vantaggi della programmazione puramente funzionale rispetto alla programmazione imperativa derivano dal fatto che il ragionamento imperativo è difficile . Alla fine della giornata, ragionare sulla correttezza del codice usando mandlebrot nonostante sia "puro" (sorta di) richiederebbe ancora un ragionamento imperativo. (Ovviamente, poiché l'implementazione di mandelbrot è imperativa, sei comunque obbligato a utilizzare il ragionamento imperativo per verificarne la correttezza.)

Se hai preso un approccio più simile, ad esempio, ad Haskell IO monad, che indirizzava i commenti su "non restituire nulla", e restituiva una rappresentazione (generale) di cosa fare, dovresti essere in gran parte o interamente richiesto di impiegare tecniche di ragionamento imperativo per verificare che il tuo programma sia corretto. Questo è il motivo per cui programmare in IO monad in Haskell non è molto più semplice di una programmazione imperativa in un linguaggio tipico. D'altra parte, se si restituisce una rappresentazione più semplice e più specializzata di ciò che dovrebbe essere fatto, una tecnica comunemente usata in Haskell, allora si potrebbe usare molto più strongmente tecniche di ragionamento che si applicano solo al codice puramente funzionale. Ad esempio, è possibile restituire una serie di pixel da aggiornare. Questa tecnica finisce per produrre alcuni degli stessi vantaggi dell'approccio a capacità di oggetti / dipendenza-in-dipendenza, spingendo tutto il lavoro che richiede un'autorità a una funzione di "interprete". L'implementazione di tale funzione interprete, tuttavia, può ancora trarre grandi vantaggi dalle tecniche di capacità degli oggetti.

In generale, consiglio vivamente di fingere (se non è proprio così) che le varie autorità siano accessibili solo attraverso le capacità. Nella misura in cui il presupposto che si sta lavorando in un sistema di capacità degli oggetti, si ottengono significative semplificazioni pur avendo un codice flessibile.

    
risposta data 28.11.2016 - 22:33
fonte