Usare le code per disaccoppiare le funzioni / evitare le chiamate dirette?

7

Tipo di domanda per principianti di programmazione funzionale qui:

Ho letto le trascrizioni di alcuni dei discorsi di Rich Hickey, e in molti dei suoi più noti, raccomanda di usare le code come alternativa alle funzioni che si chiamano l'un l'altro. (Ad esempio in Progettazione, composizione e prestazioni e in Semplice semplificato .)

Non lo capisco abbastanza, per un certo numero di aspetti:

  1. Sta parlando di mettere i dati in una coda e poi farli utilizzare da ciascuna funzione? Quindi, invece della funzione Una funzione di chiamata B per eseguire il proprio calcolo, abbiamo semplicemente la funzione B eseguire lo slap del suo output su una coda e quindi avere la funzione A afferrarla? Oppure, in alternativa, stiamo parlando di mettere le funzioni in coda e poi applicarle successivamente ai dati (sicuramente no, perché ciò implicherebbe una mutazione massiccia, giusto? E anche la moltiplicazione delle code per le funzioni multi-arità, o come alberi o qualcosa del genere? )

  2. In che modo ciò rende le cose più semplici? La mia intuizione sarebbe che questa strategia creerebbe più complessità, perché la coda sarebbe una sorta di stato, e quindi ti devi preoccupare "e se qualche altra funzione si intrufolasse e mettesse alcuni dati in cima alla coda?"

Una risposta a una domanda di implementazione su SO suggerisce che l'idea sta creando un sacco di code diverse. Quindi ogni funzione mette il suo output nella propria coda (??). Ma anche questo mi confonde, perché se si esegue una funzione una volta, allora perché ha bisogno di una coda per il suo output quando si può semplicemente prendere quell'output e schiaffare un nome su di esso come un (var, atom, entry in a big tabella hash, qualunque cosa). Al contrario, se una funzione è in esecuzione più volte e si configura l'output su una coda, si ha nuovamente lo stato su te stesso, e ci si deve preoccupare dell'ordine in cui tutto viene chiamato, le funzioni downstream diventano meno pure, ecc.

Chiaramente non sto capendo il punto qui. Qualcuno può spiegare un po '?

    
posta Paul Gowder 14.04.2016 - 17:52
fonte

2 risposte

5

È più un esercizio di progettazione che una raccomandazione generale. Di solito non si mette una coda tra tutte le chiamate di funzione diretta. Sarebbe ridicolo. Tuttavia, se non si progettano le proprie funzioni come se una coda potesse essere inserita tra una qualsiasi delle chiamate di funzione diretta, non si può legittimamente affermare di aver scritto codice riutilizzabile e componibile. Questo è il punto che sta facendo Rich Hickey.

Questo è il motivo principale del successo di Apache Spark , ad esempio. Si scrive codice che sembra che stia effettuando chiamate di funzione dirette su raccolte locali e che il framework traduca quel codice in messaggi di passaggio sulle code tra i nodi del cluster. Il tipo di stile di codifica semplice, componibile e riutilizzabile di Rich Hickey rende questo possibile.

    
risposta data 14.04.2016 - 19:12
fonte
0

Una cosa da notare è che la programmazione funzionale consente di collegare le funzioni l'un l'altra indirettamente attraverso oggetti mediatore che si occupano di procurarsi argomenti per alimentare le funzioni e in modo flessibile instradare i risultati a destinatari che desiderano i loro risultati. Quindi supponiamo di avere un semplice codice di chiamata diretta simile a questo esempio, in Haskell:

myThing :: A -> B -> C
myThing a b = f a (g a b)

Bene, usando la classe Applicative di Haskell e gli operatori <$> e <*> possiamo riscrivere meccanicamente quel codice per questo:

myThing :: Applicative f => f A -> f B -> f C
myThing a b = f <$> a <*> (g <$> a <*> b)

... dove ora myThing non sta più chiamando direttamente f e g , ma piuttosto li collega tramite alcuni mediatori di tipo f . Ad esempio, f potrebbe essere del tipo Stream fornito da una libreria che fornisce un'interfaccia a un sistema di accodamento, nel qual caso avremmo questo tipo:

myThing :: Stream A -> Stream B -> Stream C
myThing a b = f <$> a <*> (g <$> a <*> b)

Sistemi come questo esistono. Infatti, puoi consultare i Java 8 stream come una versione di questo paradigma. Ottieni il codice in questo modo:

List<Integer> transactionsIds = 
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Qui state usando le seguenti funzioni:

  • t -> t.getType() == Transaction.GROCERY
  • comparing(Transaction::getValue).reversed()
  • Transaction::getId
  • toList()

... e invece di chiamarli direttamente, stai usando il sistema Stream per mediare tra di loro. Questo esempio di codice non chiama direttamente la funzione Transaction::getId : la Stream la sta chiamando con le transazioni che sono sopravvissute al precedente filter . Puoi pensare a Stream come a un tipo di coda molto minimale che accoppia le funzioni in modo indiretto e instrada i valori tra loro.

    
risposta data 21.04.2016 - 23:46
fonte

Leggi altre domande sui tag