Come si può fare qualcosa di utile senza stato mutabile?

Ho letto un sacco di cose su di programmazione funzionale, ultimamente, e sono in grado di capire la maggior parte di esso, ma l’unica cosa che non posso avvolgere la mia testa intorno è stateless di codifica. Mi sembra che, semplificando la programmazione rimuovendo stato mutabile, è come “semplificazione” una macchina rimuovendo il cruscotto: il prodotto finito potrebbe essere più semplice, ma buona fortuna, facendolo interagire con gli utenti finali.

Appena circa ogni utente applicazione posso pensare coinvolge stato come un concetto di base. Se si scrive un documento (o un post), le variazioni di stato con ogni nuovo ingresso. O se la riproduzione di un video gioco, ci sono un sacco di variabili di stato, che inizia con le posizioni di tutti i personaggi, che tendono a muoversi costantemente. Come si può eventualmente fare qualcosa di utile, senza tenere traccia di cambiare i valori?

Ogni volta che trovo qualcosa in cui si discute di questo problema, è scritto davvero funzionale tecnica-ese che si assume una pesante FP sfondo che non ho. Qualcuno sa un modo per spiegare questo a qualcuno con una buona, solida comprensione di un imperativo di codifica, ma che è un completo n00b dal punto di vista funzionale?

EDIT: UN po ‘ di risposte finora sembrano essere cercando di convincere me i vantaggi di valori immutabili. Credo che non parte. Questo ha il senso perfetto. Quello che non capisco è come è possibile tenere traccia dei valori che devono cambiare, e cambiare costantemente, senza mutevoli variabili.

  • Vedere questo: stackoverflow.com/questions/844536/…
  • La mia personale opinione è che sembra di forza e di denaro. La legge dei rendimenti decrescenti applicare. Se sei abbastanza forte, ci può essere un piccolo incentivo per ottenere un po ‘ più forte, ma non fa male a lavorare (e alcune persone con una passione). Lo stesso vale per globale stato mutabile. È la mia personale preferenza per accettare che la mia codifica abilità progredisce è bene ridurre la quantità globale stato mutabile nel mio codice. Non può mai essere perfetto, ma è bene lavorare verso la minimizzazione globale stato mutabile.
  • Come con il denaro, un punto sarà raggiunto sono stati investire più tempo in esso, non è più molto utile e altre priorità salire verso l’alto. Se, per esempio, si raggiunge la massima forza possibile (per la mia metafora) potrebbe non servire qualsiasi scopo utile e potrebbe anche diventare un peso. Ma è ancora un tendere verso che forse irraggiungibile e investire moderato risorse in esso.
  • Brevemente, in FP, di funzioni, di non modificare mai stato. Alla fine torneranno a qualcosa che sostituisce l’attuale stato. Ma lo stato non è mai modificato (mutato) sul posto.

 

17 Replies
  1. 146

    O se la riproduzione di un video gioco, ci sono
    tonnellate di variabili di stato, a partire
    con le posizioni di tutti i
    personaggi, che tendono a muoversi
    costantemente. Come si può fare
    qualcosa di utile, senza tenere traccia
    di cambiare i valori?

    Se siete interessati, ecco una serie di articoli che descrivono la programmazione di giochi con Erlang.

    È probabilmente non piace questa risposta, ma non get programma funzionale fino a quando lo si utilizza. Mi può postare esempi di codice e dire “Qui, non è vedere” – ma se non riesci a comprendere la sintassi e principi di base, quindi i vostri occhi si offuscano. Dal tuo punto di vista, sembra come se sto facendo la stessa cosa come un linguaggio imperativo, ma solo la creazione di tutti i tipi di confini a proposito di rendere la programmazione più difficile. Il mio punto di vista, stai solo vivendo il Blub paradosso.

    Ero scettico all’inizio, ma mi è saltata la programmazione funzionale treno un paio di anni fa ed è caduto nell’amore con esso. Il trucco con la programmazione funzionale è in grado di riconoscere i modelli, in particolare, le variabili, e spostare l’imperativo di stato per lo stack. Un ciclo for, per esempio, diventa la ricorsione:

    //Imperative
    let printTo x =
        for a in 1 .. x do
            printfn "%i" a
    
    //Recursive
    let printTo x =
        let rec loop a = if a <= x then printfn "%i" a; loop (a + 1)
        loop 1
    

    Non è molto bella, ma abbiamo ottenuto lo stesso effetto con nessuna mutazione. Naturalmente, ove possibile, noi come evitare il loop del tutto e solo astratto via:

    //Preferred
    let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)
    

    Seq.iter metodo di enumerare, attraverso la raccolta e richiamare la funzione anonima per ogni elemento. Molto utile 🙂

    So, la stampa di numeri non è esattamente impressionante. Tuttavia, è possibile utilizzare lo stesso approccio con i giochi: in possesso di tutto lo stato dello stack e creare un nuovo oggetto con le nostre modifiche nella chiamata ricorsiva. In questo modo, ogni fotogramma è un apolide istantanea del gioco, dove ogni fotogramma semplicemente crea un nuovo oggetto con le modifiche di qualunque tipo stateless oggetti, necessita di un aggiornamento. Lo pseudocodice per questo potrebbero essere:

    //imperative version
    pacman = new pacman(0, 0)
    while true
        if key = UP then pacman.y++
        elif key = DOWN then pacman.y--
        elif key = LEFT then pacman.x--
        elif key = UP then pacman.x++
        render(pacman)
    
    //functional version
    let rec loop pacman =
        render(pacman)
        let x, y = switch(key)
            case LEFT: pacman.x - 1, pacman.y
            case RIGHT: pacman.x + 1, pacman.y
            case UP: pacman.x, pacman.y - 1
            case DOWN: pacman.x, pacman.y + 1
        loop(new pacman(x, y))
    

    Il imperativi e funzionali, le versioni sono identiche, ma la versione funzionale chiaramente non usa stato mutabile. Il codice funzionale tiene ogni stato è tenuto nello stack — la cosa bella di questo approccio è che, se qualcosa va storto, il debug è facile, tutto quello di cui hai bisogno è una traccia dello stack.

    Adatta a qualsiasi numero di oggetti nel gioco, perché tutti gli oggetti (o collezioni di oggetti correlati) può essere reso nel loro proprio thread.

    Appena circa ogni utente di applicazione
    può pensare coinvolge stato come un core
    concetto.

    In linguaggi funzionali, piuttosto che di mutare lo stato di oggetti, abbiamo semplicemente restituisce un nuovo oggetto con le modifiche che vogliamo. La sua più efficiente di quanto non sembri. Strutture di dati, per esempio, sono molto facili da rappresentare come immutabile strutture di dati. Pile, per esempio, sono notoriamente facile da implementare:

    using System;
    
    namespace ConsoleApplication1
    {
        static class Stack
        {
            public static Stack<T> Cons<T>(T hd, Stack<T> tl) { return new Stack<T>(hd, tl); }
            public static Stack<T> Append<T>(Stack<T> x, Stack<T> y)
            {
                return x == null ? y : Cons(x.Head, Append(x.Tail, y));
            }
            public static void Iter<T>(Stack<T> x, Action<T> f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } }
        }
    
        class Stack<T>
        {
            public readonly T Head;
            public readonly Stack<T> Tail;
            public Stack(T hd, Stack<T> tl)
            {
                this.Head = hd;
                this.Tail = tl;
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Stack<int> x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null))));
                Stack<int> y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null))));
                Stack<int> z = Stack.Append(x, y);
                Stack.Iter(z, a => Console.WriteLine(a));
                Console.ReadKey(true);
            }
        }
    }
    

    Il codice sopra costruisce due immutabile liste, aggiunge assieme per creare un nuovo elenco, e aggiunge i risultati. Non mutabile è stato utilizzato in qualsiasi punto dell’applicazione. Sembra un po ‘ ingombrante, ma che è solo perché il C# è un dettagliato lingua. Ecco il programma equivalente a F#:

    type 'a stack =
        | Cons of 'a * 'a stack
        | Nil
    
    let rec append x y =
        match x with
        | Cons(hd, tl) -> Cons(hd, append tl y)
        | Nil -> y
    
    let rec iter f = function
        | Cons(hd, tl) -> f(hd); iter f tl
        | Nil -> ()
    
    let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
    let y = Cons(5, Cons(6, Cons(7, Cons(8, Nil))))
    let z = append x y
    iter (fun a -> printfn "%i" a) z
    

    Non mutabile necessario per creare e manipolare le liste. Quasi tutte le strutture di dati possono essere facilmente convertiti in equivalenti funzionali. Ho scritto una pagina qui che fornisce immutabile implementazioni di pile, code, cumuli di sinistra, alberi rosso-neri, pigro liste. Non un singolo frammento di codice contiene qualsiasi stato mutabile. Per “mutare” un albero, ho creato uno nuovo con il nuovo nodo voglio — questo è molto efficiente in quanto non ho bisogno di fare una copia di ogni nodo dell’albero, posso riutilizzare quelli vecchi nel mio nuovo albero.

    Utilizzando un esempio più significativo, ho scritto anche questo parser SQL che è totalmente un apolide (o almeno mio il codice è stateless, non so se il sottostante lexing biblioteca è stateless).

    Apolide di programmazione è altrettanto espressiva e potente come stateful di programmazione, si richiede solo un po ‘ di pratica per imparare a iniziare a pensare statelessly. Naturalmente, “apolide di programmazione, quando possibile, stateful programmazione, ove necessario” sembra essere il motto di più impuro linguaggi funzionali. Non c’è niente di male nel cadere di nuovo sul mutables quando l’approccio funzionale solo che non è pulito o efficiente.

    • Mi piace il Pacman esempio. Ma che potrebbe risolvere un solo problema per sollevare un’altra: che Cosa se qualcosa d’altro contiene un riferimento all’esistente Pacman oggetto? Quindi non si garbage collection e sostituito; invece si finisce con due copie di un oggetto, di cui uno non è valido. Come si fa a gestire questo problema?
    • Ovviamente, è necessario creare un nuovo “qualcos’altro” con il nuovo Pacman oggetto 😉 certo, se prendiamo la rotta troppo, si finisce per ricreare l’oggetto grafico per tutto il nostro mondo, ogni volta che qualcosa cambia. Un approccio migliore è quello descritto qui (prog21.dadgum.com/26.html): invece di avere oggetti di aggiornare se stessi e tutte le loro dipendenze, la sua molto più facile per farli passare messaggi riguardo il loro stato di un ciclo di eventi che gestisce tutti gli aggiornamenti. Questo rende molto più facile decidere quali oggetti nel grafico necessita di un aggiornamento, e quelli che non.
    • Ho un dubbio, nel mio totalmente imperativo mentalità, la ricorsione deve finire a un certo punto, altrimenti alla fine ti produrre un overflow dello stack. In ricorsiva pacman esempio, come è nello stack tenuta a bada, è l’oggetto implicitamente spuntato all’inizio della funzione?
    • bella domanda … se è una “coda di chiamata”, cioè la chiamata ricorsiva è l’ultima cosa nella funzione … quindi il sistema non ha bisogno di generare un nuovo stack frame … come si può riutilizzare il precedente. Questo è un comune di ottimizzazione per i linguaggi di programmazione funzionale. en.wikipedia.org/wiki/Tail_call
    • grazie del chiarimento, questo per me – mi dispiace per ringraziare così tardi
    • Grazie per il link per la serie di articoli sulla programmazione di un gioco in Erlang. Oro puro.
    • Utile spiegazione, ma che dire di uno stato persistente? Come sarebbe di costruire un’applicazione che gestisce i record dei clienti? E come fa un programma funzionale di gestire un’enorme quantità di dati, come un motore di ricerca Internet che in genere è necessario costruire un database distribuito? E se la risposta è in qualche modo che il tutto viene mantenuto in memoria sullo stack, quindi come fare programmi funzionali recuperare da un guasto hardware?
    • quando l’interazione con i database e le Api, c’è sempre un ‘mondo esterno’, che è stato per comunicare con. In questo caso, non si può andare funzionanti al 100%. È importante mantenere questo ‘unfunctional’ codice isolato e svincolato quindi c’è una sola entrata e una di uscita verso il mondo esterno. In questo modo è possibile mantenere il resto del codice funzionale.
    • è facile, tutto quello di cui hai bisogno è una traccia dello stack (come funziona in caso di ricorsione di coda?
    • Vorrei modificare questo me stesso, ma io non sono sicuro di avere ragione, e non vorrei fare un passo su le dita dei piedi. Quando si dice “Il codice sopra costruisce due immutabile liste, aggiunge assieme per creare un nuovo elenco, e aggiunge i risultati.” — intendi, per la console, giusto? L’obiettivo esplicito nell’ex clausola rende l’oggetto implicita in quest’ultimo più difficile da determinare (disambigua del metodo denominato “append” rispetto al flusso di inserimento.) Re. di dati di grandi dimensioni: forse considerare una snapshot della macchina virtuale come immutabile stato; l’istantanea è const wrt. la sessione ma non wrt. unire/le operazioni di commit.
    • Questo è stato già discusso (@reteptilian etc.) ma credo che l’idea di fondo è che in un singolo stack, si avrebbe lo stato di base, diff, e un nuovo stato di essere utilizzato come base dello stato per la coda di chiamata. A quel punto un’affermazione di qualche tipo, avrebbe rivelato che il prodotto non è valido, e lo stack contiene l’esatto passaggio da uno stato valido per uno invalido. Altrimenti, sì, TCO vorrei lasciare una traccia dello stack solo come utile con il mutevole stato – sei bloccato con l’autopsia. Disclaimer – sono abbastanza nuovo a FP, & di debug (lavorando e che mi ha portato qui.)
    • Stato non sparire completamente dai programmi, ma funzionali, programmi tendono a spingere fuori, & rendere più esplicita possibile, per esempio, funzioni ora di stato e azioni e stato restituito. E poi abbiamo anche fantasia strutture di dati sotto il cofano per il mantenimento di valori.

  2. 73

    Risposta breve: non si può.

    Così che cosa è il polverone sull’immutabilità poi?

    Se sei esperto nel linguaggio imperativo, poi si sa che “globals sono cattivi”. Perché? Perché non introdurre (o hanno il potenziale per introdurre) alcune molto difficili da districare le dipendenze nel codice. E le dipendenze non sono buone; si desidera che il codice sia modulare. Parti di programma non influenzare altre parti meno possibile. E FP porta il santo graal della modularità: non ha effetti collaterali a tutti. Basta la tua f(x) = y. Mettere x in, ottenere y out. Nessuna modifica x o qualsiasi altra cosa. FP ti fa smettere di pensare di stato, e iniziare a pensare in termini di valori. Tutte le funzioni semplicemente ricevere valori e produrre nuovi valori.

    Questo ha diversi vantaggi.

    Prima di tutto, nessun effetti collaterali significa programmi più semplice, più facile ragionarci su. Non preoccupante che l’introduzione di una nuova parte del programma è di andare a interferire e a crash di un esistente, la sua parte di lavoro.

    Secondo, questo rende il programma banalmente possibile parallelizzare (efficiente la parallelizzazione è un altro discorso).

    Terzo, ci sono alcuni possibili vantaggi in termini di prestazioni. Supponiamo di avere una funzione:

    double x = 2 * x
    

    Ora mettete in un valore di 3, e si ottiene un valore di 6. Ogni tempo. Ma si può fare in imperativo, giusto? Yep. Ma il problema è che in imperativo, si può fare anche più. Che posso fare:

    int y = 2;
    int double(x){ return x * y; }
    

    ma potrei anche fare

    int y = 2;
    int double(x){ return x * (y++); }
    

    L’imperativo compilatore non so se ho intenzione di avere effetti collaterali o non, che lo rende più difficile da ottimizzare (ovvero il doppio di 2, non deve essere 4 ogni volta). Funzionale si sa io non sono – quindi, è in grado di ottimizzare ogni volta che si vede “doppia 2”.

    Ora, anche se la creazione di nuovi valori ogni volta sembra incredibilmente dispendioso per i tipi complessi di valori in termini di memoria del computer, ma non deve essere così. Perché, se si dispone di f(x) = y e i valori di x e y sono “lo stesso” (ad esempio, alberi che si differenziano solo per un paio di foglie), allora x e y possono condividere parti di memoria – perché nessuno di essi possa mutare.

    Quindi, se questa unmutable cosa è così grande, perché non mi rispondere che non si può fare nulla di utile senza stato mutabile. Beh, senza mutevolezza, l’intero programma sarebbe un gigante f(x) = y funzione. E lo stesso dovrebbe andare per tutte le parti del programma: basta che funzioni, e funzioni il “puro” senso che. Come ho detto, questo significa che f(x) = y ogni tempo. Così ad esempio readFile(“myFile.txt”) avrebbero bisogno di restituire lo stesso valore di stringa ogni volta. Non troppo utile.

    Pertanto, ogni FP fornisce alcuni mezzo della mutazione di stato. “Puro” linguaggi funzionali (ad esempio, Haskell) farlo con un po ‘ spaventoso concetti come monadi, mentre “impuro” (ad es. ML) consentire a questa direttamente.

    E, naturalmente, i linguaggi funzionali, dotate di una serie di altre chicche che rendono la programmazione più efficiente, come di prima classe, funzioni etc.

    • <<readFile(“myFile.txt”) avrebbero bisogno di restituire lo stesso valore di stringa ogni volta. Non troppo utile.>> sto indovinando che è utile fintanto che si nasconde globale di un filesystem. Se si considerano come un secondo parametro e lasciare che gli altri processi di restituire un nuovo punto di riferimento per il filesystem ogni volta che si modifica con filesystem2 = write(filesystem1, fd, pos, “stringa”), e lasciare che tutti i processi di cambio di riferimento per il filesystem, si potrebbe ottenere un immagine molto più pulita del sistema operativo.
    • questo è lo stesso approccio Datomic prende per i database.
    • +1 per la chiara e concisa confronto tra paradigmi. Un suggerimento è int double(x){ return x * (++y); } dato che quella attuale sarà ancora 4, pur essendo un unadvertised effetto collaterale, considerando che ++y tornerà 6.
    • Io non sono sicuro di un’alternativa, in realtà, è di chiunque altro? Per inserire i dati in un (puro) FP contesto, si “misura”, ad esempio “al timestamp X, la temperatura è Y”. Se qualcuno chiede per la temperatura, che può essere implicitamente significa che X=ora, ma non possono, eventualmente, chiedere per la temperatura come una funzione universale di tempo, giusto? FP offerte con immutabile stato, ed è necessario creare un immutabile stato – interni e sorgenti esterne – da un mutevole uno. Gli indici, gli orari, etc. sono utili, ma ortogonale alla mutevolezza – come VCS sono per il controllo della versione stessa.
  3. 25

    Notare che dicendo di programmazione funzionale, non sono ‘state’ è un po ‘ fuorviante e potrebbe essere la causa della confusione. Sicuramente non ha ‘stato mutabile’, ma è ancora possibile avere i valori che sono manipolati; solo che non può essere modificata in sede (ad esempio, è necessario creare nuovi valori da vecchi valori).

    Questa è una grossolana semplificazione, ma immagina di aver un linguaggio OO, dove tutte le proprietà delle classi di impostare una sola volta nel costruttore, tutti i metodi sono funzioni statiche. Si potrebbe ancora fare praticamente qualsiasi calcolo avendo metodi di oggetti che contiene tutti i valori hanno bisogno per i loro calcoli e poi tornare di nuovo gli oggetti con il risultato (forse una nuova istanza dell’oggetto stesso anche).

    Può essere “duro” per tradurre il codice esistente in questo paradigma, ma che è perché richiede davvero un modo completamente diverso di pensare il codice. Come effetto collaterale anche se nella maggior parte dei casi si ottiene un sacco di opportunità per il parallelismo per libero.

    Addendum: (per quanto Riguarda la modifica di come tenere traccia di valori che devono cambiare)

    Essi potrebbero essere memorizzati in un immutabile struttura di dati di corso…

    Questa non è una proposta di “soluzione”, ma il modo più semplice per vedere che questo lavoro è che è possibile memorizzare questi valori immutabili in una mappa (dizionario /hashtable) come struttura, con la chiave da un ‘nome di variabile’.

    Ovviamente in soluzioni pratiche che si potrebbe utilizzare un più sano approccio, ma questo non mostrano che nel caso peggiore, se non altro per farle funzionare si potrebbe ‘simulare’ stato mutabile, con una mappa che ti porti dietro tramite il tuo invocazione albero.

    • OK, ho cambiato il titolo. La tua risposta sembra portare ad una ancora peggiore problema, però. Se devo ricreare ogni oggetto, ogni volta che qualcosa nel suo stato cambia, passerò tutto il mio tempo di CPU non fare nulla ma costruire oggetti. Sto pensando di programmazione di videogiochi qui, dove hai un sacco di cose a muoversi sullo schermo (e off-screen) in una sola volta, che devono essere in grado di interagire con gli altri. Tutto il motore è dotato di un set di framerate: tutto ciò che stai andando a fare, si deve fare X numero di millisecondi. Sicuramente c’è un modo migliore di continuo riciclo di oggetti interi?
    • Il bello è che il imutability è la lingua, non l’implementazione. Con pochi accorgimenti, si può avere imutable stato nel linguaggio, mentre l’attuazione è, infatti, di cambiare lo stato in luogo. Vedere, per esempio, Haskell ST monade.
    • Il punto è che il compilatore è in grado di molto meglio decidere dove (filo), cassetta di sicurezza per cambiare lo stato in luogo che non è possibile.
    • Penso che per i giochi si dovrebbe evitare di immutabile per tutte le parti in cui la velocità non è un problema. Mentre un immutabile lingua, può ottimizzare per voi, nulla sarà più veloce rispetto alla modifica di una memoria che le Cpu sono veloci a fare. E quindi, se si scopre che ci sono 10 o 20 posti in cui avete bisogno imperativo penso che si dovrebbe solo evitare di immutabile, complessivamente, a meno che non si può modularizzare per molto separati aree come il menu di gioco. E la logica del gioco, in particolare, potrebbero essere un bel posto da utilizzare immutabile, perché sento che è ottimo per fare complessi, la modellazione di puro sistemi come le regole di business.
    • ti stai contraddicendo te stesso.
  4. 15

    Penso che ci sia una leggera incomprensione. Puro programmi funzionali sono stato. La differenza è il modo che è stato modellato. In puro programmazione funzionale, è stato manipolato da funzioni che prendono qualche stato e di tornare per il prossimo stato. Sequenziamento attraverso stati poi raggiunti da un passaggio di stato attraverso una sequenza di funzioni pure.

    Anche globale stato mutabile, può essere modellato in questo modo. In Haskell, per esempio, un programma è una funzione da a in un nuovo Mondo. Che è, si passa in l’intero universo, e il programma restituisce un nuovo universo. In pratica, però, è solo bisogno di passare nelle parti dell’universo in cui il programma è realmente interessato. E i programmi effettivamente restituire una sequenza di azioni che servono come istruzioni per l’ambiente operativo in cui viene eseguito il programma.

    Volevi vedere questo spiegato in termini di programmazione imperativa. OK, diamo un’occhiata a qualche semplice imperativo di programmazione in un linguaggio funzionale.

    Considerare questo codice:

    int x = 1;
    int y = x + 1;
    x = x + y;
    return x;
    

    Abbastanza bog-standard codice imperativo. Non fare nulla di interessante, ma che è OK per l’illustrazione. Penso che sarete d’accordo che c’è stato qui. Il valore della variabile x modifiche nel corso del tempo. Ora, proviamo a cambiare la notazione leggermente, inventando una nuova sintassi:

    let x = 1 in
    let y = x + 1 in
    let z = x + y in z 
    

    Parentesi per rendere più chiaro ciò che questo significa:

    let x = 1 in (let y = x + 1 in (let z = x + y in (z)))
    

    Quindi, come vedi, è stato modellato da una sequenza di puro espressioni che legano le variabili libere delle seguenti espressioni.

    Troverete che questo modello può modellare qualsiasi tipo di stato, e anche IO.

    • È che, come in una specie di Monade?
    • Vuoi prendere in considerazione questo: Una è dichiarativa al livello 1 B dichiarativa al livello 2, si considera Un essere imperativo. C è dichiarativa al livello 3, si ritiene B per essere un imperativo. Come aumentare il livello di astrazione, considera sempre lingue inferiore sul livello di astrazione più imperativo quindi di per sé.
  5. 11

    Ecco come si scrive il codice, senza stato mutabile: invece di mettere la modifica dello stato di fatto in mutevoli variabili, si è messo nei parametri di funzioni. E invece di scrivere un loop, è scrivere funzioni ricorsive. Così, per esempio, questo codice imperativo:

    f_imperative(y) {
      local x;
      x := e;
      while p(x, y) do
        x := g(x, y)
      return h(x, y)
    }
    

    diventa questo codice funzionale (Schema-come sintassi):

    (define (f-functional y) 
      (letrec (
         (f-helper (lambda (x y)
                      (if (p x y) 
                         (f-helper (g x y) y)
                         (h x y)))))
         (f-helper e y)))
    

    o questo Haskellish codice

    f_fun y = h x_final y
       where x_initial = e
             x_final   = loop x_initial
             loop x = if p x y then loop (g x y) else x
    

    Per perché funzionali e programmatori come fare questo (non chiedere), il più pezzi di un programma sono apolidi, più modi ci sono per mettere insieme i pezzi senza avere niente pausa. Il potere di apolide paradigma non sta in apolidia (purezza) di per sé, ma la possibilità che dà a scrivere potente, riutilizzabili funzioni e combinarli.

    Si può trovare un buon tutorial con un sacco di esempi di John Hughes carta Perché La Programmazione Funzionale Questioni.

  6. 10

    È solo modi diversi di fare la stessa cosa.

    Consideriamo un esempio semplice come l’aggiunta di numeri 3, 5 e 10. Immaginare pensando di fare che prima di cambiare il valore di 3, con l’aggiunta di 5, quindi l’aggiunta di 10 a quel “3”, quindi l’output il valore corrente di “3” (18). Questo sembra palesemente ridicolo, ma è in sostanza il modo in cui stato e di programmazione imperativo è spesso fatto. Infatti, si possono avere molte e diverse “3”s che hanno il valore di 3, ma sono diversi. Tutto questo sembra strano, perché siamo stati così radicata con l’, abbastanza enormemente sensibile, l’idea che i numeri sono immutabili.

    Ora pensare di aggiungere, 3, 5, e 10, quando si prendono i valori immutabili. Si aggiunge il 3 e il 5 per produrre un valore di, 8, poi aggiungere 10 a quella di produrre ancora un altro valore, 18.

    Queste sono equivalenti modi per fare la stessa cosa. Tutte le informazioni necessarie esiste in entrambi i metodi, ma in forme diverse. In uno l’informazione esiste come stato e nelle regole per il cambio di stato. In altri le informazioni presente nell’immutabile dati e definizioni funzionali.

  7. 7

    Sono in ritardo per la discussione, ma volevo aggiungere un paio di punti per persone che sono alle prese con la programmazione funzionale.

    1. Linguaggi funzionali mantenere esattamente la stessa aggiornamenti di stato come linguaggi imperativi, ma lo fanno passando l’aggiornamento dello stato per le successive chiamate di funzione. Qui è un esempio molto semplice di viaggio su una linea numero. Il tuo stato è la vostra posizione corrente.

    Primo imperativo (in pseudocodice)

    moveTo(dest, cur):
        while (cur != dest):
             if (cur < dest):
                 cur += 1
             else:
                 cur -= 1
        return cur
    

    Ora il funzionale (in pseudocodice). Sono appoggiato pesantemente l’operatore ternario, perché voglio che la gente da imperativo sfondi per essere effettivamente in grado di leggere il codice. Quindi, se non si usa l’operatore ternario molto (ho sempre evitato nel mio imperativo giorni) ecco come funziona.

    predicate ? if-true-expression : if-false-expression
    

    È possibile catena ternario espressione, mettendo un nuovo ternario espressione in luogo del falso-espressione

    predicate1 ? if-true1-expression :
    predicate2 ? if-true2-expression :
    else-expression
    

    Quindi, con questo in mente, ecco la versione funzionale.

    moveTo(dest, cur):
        return (
            cur == dest ? return cur :
            cur < dest ? moveTo(dest, cur + 1) : 
            moveTo(dest, cur - 1)
        )
    

    Questo è un banale esempio. Se questo fosse il movimento di persone in giro in un mondo di gioco, sarebbe necessario introdurre effetti collaterali come il disegno dell’oggetto corrente posizione sullo schermo e l’introduzione di un po ‘ di ritardo nelle chiamate in base a quanto velocemente l’oggetto si muove. Ma ancora non stato mutabile.

    1. La lezione è che i linguaggi funzionali “mutare” stato chiamando la funzione con parametri diversi. Ovviamente questo non mutare di tutte le variabili, ma come si ottiene un effetto simile. Questo significa che potrete avere per abituarsi a pensare in modo ricorsivo se si desidera eseguire la programmazione funzionale.

    2. Imparare a pensare in modo ricorsivo non è difficile, ma ci vuole pratica, e un kit di strumenti. Che piccola sezione che “Imparare il Java” libro in cui hanno usato la ricorsione per calcolare il fattoriale non è tagliato. Avete bisogno di un insieme di capacità come fare processi iterativi di ricorsione (questo è il motivo per cui ricorsività è essenziale per il linguaggio funzionale), continuazioni, invarianti, etc. Non puoi non farlo programmazione OO senza conoscere i modificatori di accesso, interfacce etc. Stessa cosa per la programmazione funzionale.

    La mia raccomandazione è di fare il Piccolo Intrigante (nota che ho detto “fare” e non “leggere”) e poi fare tutti gli esercizi SICP. Quando hai finito, avrete un diverso cervello rispetto a quando hai iniziato.

  8. 6

    Programmazione funzionale evita stato e sottolinea funzionalità. Non c’è mai alcuna cosa come nessuno stato, se lo stato potrebbe in realtà essere qualcosa di immutabile, al forno o in architettura di cosa si sta lavorando. Si consideri la differenza tra statico del server web che semplicemente carica file dal filesystem contro un programma che implementa un cubo di Rubik. L’ex sta per essere implementato in termini di funzioni progettate per girare una richiesta in un percorso di file richiesta in una risposta dal contenuto di quel file. Praticamente nessuno stato è necessario, al di là di un po ‘ di configurazione (il filesystem di ‘stato’ è davvero al di fuori del campo di applicazione del programma. Il programma funziona allo stesso modo, indipendentemente da quale stato i file). In quest’ultimo, però, è necessario modellare il cubo e il programma di attuazione di come operazioni sul cubo cambiare il suo stato.

    • Quando ero più anti-funzionale, mi chiedevo come poteva essere buona quando qualcosa di simile a un disco rigido è mutevole. Il c# classi di tutti era stato mutabile, e potrebbe logicamente simulare un disco rigido o qualsiasi altro dispositivo. Considerando che con funzionale c’è stata una mancata corrispondenza tra i modelli e le macchine reali erano di modellazione. Dopo approfondire funzionale, sono venuto a rendersi conto dei vantaggi che si ottengono sono in grado di superare tale problema un bel po’. E se fosse fisicamente possibile inventare un disco rigido che ha fatto una copia di se stesso che sarebbe in realtà essere utile (come journalling già lo fa).
  9. 6

    È in realtà abbastanza facile avere qualcosa che assomiglia stato mutabile, anche in lingue senza stato mutabile.

    Considerare una funzione di tipo s -> (a, s). Traduzione dal Haskell sintassi, significa che una funzione che accetta un parametro di tipo “s” e restituisce una coppia di valori, di ” tipi dia” e “s“. Se s è il tipo del nostro stato, questa funzione prende e restituisce un nuovo stato, e, eventualmente, un valore (si può sempre tornare, “l’unità” aka (), che è una sorta di equivalente a “void” in C/C++, come il “a” tipo). Se la catena di diverse chiamate di funzioni con tipi come questo (sempre stato restituito da una funzione e passando per la prossima), si sono “mutevole” stato ” (in realtà si sono in ogni funzione di creazione di un nuovo stato e di abbandonare il vecchio).

    Potrebbe essere più facile da capire se si immagina lo stato mutabile, come lo “spazio” in cui il programma è in esecuzione, e poi pensare alla dimensione temporale. All’istante t1, lo “spazio” in una certa condizione (diciamo, per esempio, della posizione di memoria del valore di 5). In un secondo istante t2, è in una condizione diversa (per esempio che la posizione di memoria ora ha valore 10). Ciascuna di queste “fette” è stato, ed è immutabile (non si può andare indietro nel tempo per modificare il loro). Quindi, da questo punto di vista, è andato dal pieno spazio-tempo con una freccia del tempo (il tuo stato mutabile) a una serie di fette di spazio-tempo (diversi immutabile uniti), e il programma è solo il trattamento di ogni fetta come un valore e calcolo di ciascuno di loro come una funzione applicata al precedente.

    OK, forse non era più facile da capire 🙂

    Potrebbe sembrare inneficient per rappresentare in modo esplicito tutto il programma è stato come un valore, che deve essere creata solo per essere scartato l’istante successivo (subito dopo ne viene creato uno nuovo). Per alcuni algoritmi potrebbe essere naturale, ma quando non lo è, c’è un altro trucco. Invece di un vero e proprio stato, è possibile utilizzare un falso stato, che non è altro che un marcatore (diciamo il tipo di questo falso stato State#). Questo falso stato esiste dal punto di vista della lingua, ed è passata come un qualsiasi altro valore, ma il compilatore ometta completamente quando la generazione del codice macchina. Serve solo per segnare la sequenza di esecuzione.

    Come esempio, supponiamo che il compilatore ci dà le seguenti funzioni:

    readRef :: Ref a -> State# -> (a, State#)
    writeRef :: Ref a -> a -> State# -> (a, State#)
    

    Tradurre da questi Haskell-come dichiarazioni, readRef riceve qualcosa che assomiglia a un puntatore o un handle per un valore di tipo “a“, e il falso stato, e restituisce un valore di tipo “a” ha sottolineato il primo parametro e un nuovo falso stato. writeRef è simile, ma cambia il valore puntato invece.

    Se si chiama readRef e poi passare il falso stato restituito da writeRef (forse con le altre chiamate verso estranei funzioni nel mezzo; questi i valori dello stato di creare una “catena” di chiamate di funzione), verrà restituito il valore scritto. È possibile chiamare writeRef di nuovo con lo stesso puntatore/maniglia e scriverà la stessa posizione di memoria — ma, dal momento che concettualmente è la restituzione di una nuova (falso) stato, il (falso) è stato ancora imutable (uno nuovo è stato “creato”). Il compilatore chiamare le funzioni in ordine avrebbe dovuto chiamare loro se c’è stata una vera variabile di stato che doveva essere calcolata, ma l’unico stato che non c’è è il pieno (mutevole) lo stato dell’hardware reale.

    (Chi sa Haskell noterà che mi ha semplificato molto le cose e ommited alcuni importanti dettagli. Per coloro che vogliono vedere più dettagli, date un’occhiata a Control.Monad.State dal mtl, e al ST s e IO (aka ST RealWorld) monadi.)

    Ci si potrebbe chiedere perché farlo in una rotonda modo (invece di essere semplicemente stato mutabile nel linguaggio). Il vero vantaggio è che hai reified il programma è stato. Ciò che prima era implicito (il programma era stato globale, consentendo cose come l’azione a distanza) è ora esplicito. Funzioni non ricevere e restituire lo stato non può modificare o essere influenzati da esso; essi sono “puri”. Ancora meglio, si può avere stato separato thread, e con un po ‘ di tipo di magia, che può essere utilizzato per incorporare un imperativo di calcolo all’interno di un puro, senza renderlo impuro (il ST monade in Haskell è quello normalmente usato per questo trucco; il State# ho citato sopra è infatti GHC del State# s, utilizzato da sua attuazione dei ST e IO monadi).

  10. 4

    Oltre ai grandi risposte anche altri lo fanno, pensare le classi Integer e String in Java. Le istanze di queste classi sono immutabili, ma non per le classi inutili solo perché le loro istanze non può essere modificato. L’immutabilità ti dà una certa sicurezza. Sapete se si utilizza una Stringa o un Intero istanza come la chiave per una Map, la chiave non può essere cambiato. Confronta questo per il Date classe in Java:

    Date date = new Date();
    mymap.put(date, date.toString());
    //Some time later:
    date.setTime(new Date().getTime());
    

    Hai silenziosamente cambiato di una chiave in una mappa! Lavorare con gli oggetti immutabili, come nella Programmazione Funzionale, è molto più “pulita”. È più facile ragionare su quali sono gli effetti collaterali si verificano — nessuno! Questo significa che è più facile per i programmatori, e anche più facile per il optimizer.

    • Capisco che, ma non risponde alla mia domanda. Tenendo presente che un programma per computer è un modello di qualche evento o processo, se non è possibile cambiare i valori, allora come si fa a modellare qualcosa che cambia?
    • Beh, si può certamente fare cose utili, con il numero Intero e una Stringa classi. Non è come il loro immutabilità significa che non sono stato mutabile.
    • Wheeler – Da capire che una cosa e stato sono due diverse “cose”. Quello che pacman non cambia da momento a momento B. Dove pacman non cambia. Quando si sposta da tempo a tempo B, si ottiene una nuova combinazione di pacman + stato… che è la stessa pacman, stato diverso. Non è stato modificato… stato diverso.
  11. 3

    Per applicazioni altamente interattive, come i giochi, Funzionali Reattivi di Programmazione è un tuo amico: se è possibile formulare le proprietà del gioco del mondo come variabile nel tempo i valori (e/o flussi di eventi), si è pronti! Queste formule, sarà a volte anche più naturale e di intenti-la scoperta di mutazione di uno stato, ad esempio, per una palla in movimento, è possibile utilizzare direttamente la ben nota legge x = v * t. E cosa c’è di meglio, le regole del gioco scritto in modo tale comporre meglio di astrazioni object-oriented. Per esempio, in questo caso, la palla, la velocità può essere anche un tempo diverso valore, che dipende dal flusso di eventi che consiste la palla collisioni. Più concrete considerazioni di progettazione, vedere Giochi in Olmo.

  12. 2

    Che è il modo in FORTRAN potrebbe funzionare senza blocchi COMUNI: È necessario scrivere metodi che erano i valori passati e variabili locali. Che è.

    Di programmazione orientata agli oggetti ci ha portato lo stato e il comportamento insieme, ma era una nuova idea, quando ho incontrato per la prima volta dal C++ nel 1994.

    Accidenti, mi era funzionale programmatore quando ero un ingegnere meccanico e non lo sapevo!

    • Io sarei d’accordo che questo è qualcosa che si può pin OO. Lingue prima di OO incoraggiati accoppiamento stato e algoritmi. OO appena fornito un modo migliore per gestirlo.
    • “Incoraggiati” – forse. OO rendere esplicito parte del linguaggio. Si può fare di incapsulamento e information hiding in C, ma direi che linguaggi OO rendere molto più facile.
  13. 1

    Tenere a mente: i linguaggi funzionali sono Turing-completo. Pertanto, qualsiasi attività utile è possibile eseguire un imperitive lingua può essere fatto in un linguaggio funzionale. Alla fine della giornata, però, penso che ci sia qualcosa da dire di un approccio ibrido. Lingue come il F# e Clojure (e sono sicuro che gli altri) incoraggiare apolide, di design, ma consentire la mutevolezza quando necessario.

    • Solo perché due lingue sono Turing completo non significa che essi possono eseguire gli stessi compiti. Ciò significa che è possibile eseguire lo stesso calcolo. Brainfuck è Turing completo, ma sono abbastanza certo che non può comunicare più di uno stack TCP.
    • Certo che è possibile. Dato che lo stesso accesso all’hardware, come ad esempio C, è possibile. Che non significa che non sarebbe pratico, ma la possibilità c’è.
  14. 1

    Non si può avere il puro linguaggio funzionale che è utile. Ci sarà sempre un livello di trasformabilità che hai a che fare con, IO è un esempio.

    Pensare di linguaggi funzionali come solo un altro strumento che si utilizza. La sua buona per certe cose, ma non altri. Il gioco di esempio che hai dato potrebbe non essere il modo migliore per utilizzare un linguaggio funzionale, almeno lo schermo avrà una mutevole stato che non si può fare nulla con FP. Il modo di pensare di problema e il tipo di problemi che si risolvono con FP saranno diverse da quelle che si è abituati a con la programmazione imperativa.

  15. -3

    Questo è molto semplice. È possibile utilizzare le variabili che si desidera in programmazione funzionale…ma solo se sono locale variabili contenute all’interno di una funzione). Quindi basta racchiudere il codice in funzioni, passaggio di valori e indietro tra quelle funzioni (come in passato parametri e valori restituiti)…e che è tutto là è ad esso!

    Ecco un esempio:

    function ReadDataFromKeyboard() {
        $input_values = $_POST[];
        return $input_values;
    }
    function ProcessInformation($input_values) {
        if ($input_values['a'] > 10)
            return ($input_values['a'] + $input_values['b'] + 3);
        else if ($input_values['a'] > 5)
            return ($input_values['b'] * 3);
        else
            return ($input_values['b'] - $input_values['a'] - 7);
    }
    function DisplayToPage($data) {
        print "Based your input, the answer is: ";
        print $data;
        print "\n";
    }
    
    /* begin: */
    DisplayToPage (
        ProcessInformation (
            GetDataFromKeyboard()
        )
    );
    
    • Giovanni, che lingua è questa?

Lascia un commento