Come gestisci un buffer mutabile in uno spazio funzionale?

0

Ho un metodo di analisi dello stream in C # che legge porzioni di un frame del protocollo dal protocollo STOMP; la specifica non è la parte importante per la domanda però. Ciò che è importante è che ho scritto il codice in uno stile imperativo , poiché è ciò che so.

Qualcuno può dare alcuni esempi di elaborazione in modo funzionale? (Psuedo-code, o qualsiasi lingua costituirà una risposta accettabile)

/// <summary>
/// Reads a STOMP command string asynchronyously.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>CommandString if read; otherwise null</returns>
public async Task<string> ReadStompCommand(CancellationToken cancellationToken)
{
    string commandString = null;

    //Create a new buffer, maximum possible allowed bytes in the line is 13
    var commandBuffer = new byte[13]; 

    //EOL is the line ending as defined in the spec of STOMP 1.2
    //EOL can either be CRLF or LF alone
    var eolIndex = -1;
    var bytesRead = 0;

    //while we haven't maxed out our buffer and we haven't found an EOL sequence
    while (bytesRead < commandBuffer.Length && eolIndex == -1)
    {
        //move forward in the buffer
        var offset = bytesRead;

        //Reading 1 byte at a time for now, may change in a future implementation
        const int length = 1;
        var bytesFound = 0;

        bytesFound += await Stream.ReadAsync(commandBuffer, offset, length, cancellationToken);

        //check for EOL in the bytes we read. (1 iteration since length is const of 1)
        for (var i = offset; i < offset + bytesFound; i++)
        {
            if (commandBuffer[i] == 0x0a)
                eolIndex = i;
        }

        bytesRead += bytesFound;
    }

    //if we have a potentially meaningful line (STOMPs shortest command is 3 characters)
    if (eolIndex > 2)
    {
        //If last byte before LF was CR, move the EOL start index back one
        if (commandBuffer[eolIndex - 1] == 0x0d)
        {
            eolIndex--;
        }

        //Convert bytes to string in UTF-8 from beginning to start of EOL
        commandString = Encoding.UTF8.GetString(commandBuffer, 0, eolIndex);
    }

    return commandString;
}
    
posta Ovan Crone 28.06.2016 - 19:25
fonte

2 risposte

2

I have a stream parsing method in C# that reads portions of a protocol frame from the STOMP protocol... Can anyone give some examples of processing in a functional manner?

Stai pensando a questo al livello sbagliato.

La domanda interessante non è "come implementerei questo codice buffer di byte di basso livello in uno stile funzionale?" I dettagli di implementazione privata del metodo sono proprio questo: dettagli di implementazione privati. Non c'è alcun vantaggio per l'utente del metodo del tuo scrivere l'implementazione del metodo in uno stile funzionale.

La domanda interessante è: come posso rendere il metodo nel suo complesso più suscettibile agli utenti del metodo che desiderano programmare in uno stile funzionale ?

Perché il tuo metodo qui non è davvero funzionale. Non richiede argomenti (di alcun interesse, chiaramente il valore del token di cancellazione non influenza l'output) e restituisce una stringa differita che sarà potenzialmente diversa ogni volta che viene recuperata. O, più precisamente, restituisce una stringa differita che verrà passata a una richiamata quando è disponibile.

Quale è quindi il modo più funzionale per strutturare questo codice? Desideriamo pensare a l'intera sequenza di stringhe che verranno inserite nei callback che entreranno nel futuro come oggetto singolo . Allo stesso modo in cui pensiamo a una sequenza di elementi su richiesta - IEnumerable<T> - come a un singolo oggetto che può fornire arbitrariamente molti oggetti in futuro.

In breve, il modo più funzionale per strutturare questo codice è creare un oggetto che sia una collezione osservabile di stringhe . La cosa che vuoi cercare qui è programmazione reattiva funzionale .

Questo dovrebbe essere sufficiente per iniziare questa particolare tana del coniglio.

Bonus: una volta che hai una conoscenza di base di FRP, prova ad avvolgere la testa attorno alla monade IO in Haskell se vuoi veramente vedere come strutturare questi tipi di operazioni in uno stile puramente funzionale.

    
risposta data 30.06.2016 - 00:16
fonte
2

Per la tua specifica parte di codice, è un livello molto basso, legge i byte da un input e li scrive su un output. In C #, questo codice non sarà funzionale.

Ma puoi aggiungere un wrapper in modo da poter passare uno "stato del mondo" falso ai tuoi metodi non funzionali e far sì che restituiscano lo stato "alterato" del mondo insieme al risultato.

La firma è:

class World {}
class WorldAndResult<T> {
    World world;
    T t; // this is your normal result
    WorldAndResult(World w, T theT) { world = w; t = theT; }
}

public WorldAndResult<Int> mutator(World w, otherArgs...) {
    int ret = wrappedMutator(otherArgs...);
    return new WorldAndResult(w, ret);
}

Ora puoi concatenare queste chiamate e fingere che siano funzionali e che mutino il "Mondo". È un oggetto fittizio che si distingue per qualsiasi effetto collaterale del tuo metodo.

final WorldAndResult<Integer> war = mutator(new World(), 37);
final WorldAndResult<Integer> war2 = mutator(war.world, 54);

L'hardware del computer è indispensabile. Muta in posizione. Ha effetti collaterali: interruzioni hardware, scrittura su disco, modifica del buffer di visualizzazione, lettura e scrittura dalla scheda di rete ...

Ogni linguaggio funzionale, ogni kit di strumenti funzionali, avvolge tutte le cose imperative in un wrapper con effetti collaterali minimi che impedisce la mutazione non essenziale in atto. Quando è necessario per motivi di prestazioni, fa si modifica in posizione, ma nasconde quella mutazione dal programmatore.

La programmazione funzionale è efficiente nel grande, anche se può essere inefficiente per alcuni dettagli. Semplificare il tuo codice libera il tuo cervello per rendere più efficienti progettare scelte che lo faresti quando ti concentri su bit che girano e girano. Così comincio sempre a funzionare. È molto raro che io debba fare un secondo passaggio per accelerare qualcosa. A meno che, naturalmente, non scriva infrastrutture da utilizzare nella programmazione funzionale. : -)

    
risposta data 28.06.2016 - 23:31
fonte

Leggi altre domande sui tag