Come può esistere una funzione temporale nella programmazione funzionale?


646

Devo ammettere che non so molto sulla programmazione funzionale. Ne ho letto di qua e di là, e così ho scoperto che nella programmazione funzionale, una funzione restituisce lo stesso output, per lo stesso input, indipendentemente da quante volte viene chiamata la funzione. È esattamente come una funzione matematica che valuta lo stesso output per lo stesso valore dei parametri di input che coinvolge nell'espressione della funzione.

Ad esempio, considera questo:

f(x,y) = x*x + y; // It is a mathematical function

Non importa quante volte lo usi f(10,4), il suo valore sarà sempre 104. Pertanto, ovunque tu abbia scritto f(10,4), puoi sostituirlo con 104, senza alterare il valore dell'intera espressione. Questa proprietà viene definita trasparenza referenziale di un'espressione.

Come dice Wikipedia ( link ),

Al contrario, nel codice funzionale, il valore di output di una funzione dipende solo dagli argomenti che sono stati immessi nella funzione, quindi chiamare una funzione f due volte con lo stesso valore per un argomento x produrrà lo stesso risultato f (x) entrambe le volte.

Può esistere una funzione temporale (che restituisce l' ora corrente ) nella programmazione funzionale?

  • Se sì, come può esistere? Non viola il principio della programmazione funzionale? In particolare viola la trasparenza referenziale che è una proprietà della programmazione funzionale (se la capisco correttamente).

  • O se no, come si può sapere l'ora corrente nella programmazione funzionale?


15
Penso che la maggior parte (o tutti) i linguaggi funzionali non siano così severi e combinino programmazione funzionale e imperativa. Almeno, questa è la mia impressione da F #.
Alex F,

13
@Adam: In che modo il chiamante potrebbe sapere l'ora corrente in primo luogo?
Nawaz,

29
@Adam: In realtà è illegale (come in: impossibile) in linguaggi puramente funzionali.
sepp2k,

47
@Adam: praticamente. Un linguaggio generico che è puro di solito offre qualche possibilità per arrivare allo "stato mondiale" (cioè cose come l'ora corrente, i file in una directory ecc.) Senza rompere la trasparenza referenziale. In Haskell questa è la monade IO e in Clean è il tipo mondiale. Quindi in quelle lingue una funzione che ha bisogno del tempo attuale la prenderebbe come argomento o dovrebbe restituire un'azione IO invece del suo risultato effettivo (Haskell) o prendere lo stato del mondo come argomento (Clean).
sepp2k,

12
Quando si pensa a FP è facile dimenticare: un computer è un grosso pezzo di stato mutevole. FP non lo cambia, lo nasconde e basta.
Daniel,

Risposte:


176

Un altro modo per spiegarlo è questo: nessuna funzione può ottenere l'ora corrente (poiché continua a cambiare), ma un'azione può ottenere l'ora corrente. Diciamo che getClockTimeè una costante (o una funzione nulla, se vuoi) che rappresenta l' azione di ottenere l'ora corrente. Questa azione è la stessa ogni volta, non importa quando viene usata, quindi è una vera costante.

Allo stesso modo, diciamo che printè una funzione che richiede un po 'di tempo per la rappresentazione e la stampa sulla console. Poiché le chiamate di funzione non possono avere effetti collaterali in un linguaggio funzionale puro, immaginiamo invece che sia una funzione che prende un timestamp e restituisce l' azione di stamparlo sulla console. Ancora una volta, questa è una vera funzione, perché se si assegna lo stesso timestamp, si restituirà la stessa azione di stamparlo ogni volta.

Ora, come è possibile stampare l'ora corrente sulla console? Bene, devi combinare le due azioni. Quindi come possiamo farlo? Non possiamo semplicemente passare getClockTimea print, poiché la stampa prevede un timestamp, non un'azione. Ma possiamo immaginare che esista un operatore, >>=che combina due azioni, una che ottiene un timestamp e una che ne prende uno come argomento e lo stampa. Applicando questo alle azioni precedentemente menzionate, il risultato è ... tadaaa ... una nuova azione che ottiene l'ora corrente e la stampa. E questo è per inciso esattamente come viene fatto in Haskell.

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

Quindi, concettualmente, è possibile visualizzarlo in questo modo: un programma funzionale puro non esegue alcun I / O, ma definisce un'azione che il sistema di runtime esegue. L' azione è sempre la stessa, ma il risultato dell'esecuzione dipende dalle circostanze in cui è stata eseguita.

Non so se sia stato più chiaro delle altre spiegazioni, ma a volte mi aiuta a pensarlo in questo modo.


33
Non è convincente per me. Hai chiamato convenientemente getClockTimeun'azione anziché una funzione. Bene, se lo chiami, quindi chiama ogni azione di funzione , anche la programmazione imperativa diventerebbe programmazione funzionale. O forse, volete chiamarla Actional tecniche di programmazione.
Nawaz,

92
@Nawaz: La cosa fondamentale da notare qui è che non è possibile eseguire un'azione dall'interno di una funzione. Puoi solo combinare azioni e funzioni insieme per fare nuove azioni. L'unico modo per eseguire un'azione è comporla nella tua mainazione. Ciò consente di separare il codice funzionale puro dal codice imperativo e questa separazione viene applicata dal sistema dei tipi. Trattare le azioni come oggetti di prima classe consente anche di passarle in giro e costruire le proprie "strutture di controllo".
Hammar,

36
Non tutto in Haskell è una funzione - è una totale assurdità. Una funzione è qualcosa il cui tipo contiene un ->- è così che lo standard definisce il termine ed è davvero l'unica definizione sensata nel contesto di Haskell. Quindi qualcosa il cui tipo IO Whateverè non è una funzione.
sepp2k,

9
@ sepp2k Quindi, myList :: [a -> b] è una funzione? ;)
fuz,

8
@ThomasEding Sono davvero in ritardo alla festa, ma voglio solo chiarire questo: putStrLnnon è un'azione, è una funzione che restituisce un'azione. getLineè una variabile che contiene un'azione. Le azioni sono i valori, le variabili e le funzioni sono i "contenitori" / "etichette" che diamo a quelle azioni.
kqr,

356

Sì e no.

Diversi linguaggi di programmazione funzionale li risolvono in modo diverso.

In Haskell (molto puro) tutte queste cose devono accadere in qualcosa chiamato I / O Monade - vedi qui .

Puoi pensarlo come ottenere un altro input (e output) nella tua funzione (lo stato del mondo) o più facile come un luogo in cui accade "impurità" come ottenere il cambiamento del tempo.

Altre lingue come F # hanno solo una certa impurità incorporata e quindi puoi avere una funzione che restituisce valori diversi per lo stesso input - proprio come i normali linguaggi imperativi.

Come ha detto Jeffrey Burka nel suo commento: Ecco la bella introduzione alla Monade I / O direttamente dalla wiki di Haskell.


223
La cosa cruciale da capire sulla monade IO in Haskell è che non è solo un trucco per aggirare questo problema; le monadi sono una soluzione generale al problema di definire una sequenza di azioni in alcuni contesti. Un possibile contesto è il mondo reale, per il quale abbiamo la monade IO. Un altro contesto è all'interno di una transazione atomica, per la quale abbiamo la monade STM. Ancora un altro contesto è l'implementazione di un algoritmo procedurale (ad esempio Knuth shuffle) come una funzione pura, per la quale abbiamo la monade ST. E puoi anche definire le tue monadi. Le monadi sono una sorta di punto e virgola sovraccaricabile.
Paul Johnson,

2
Trovo utile non chiamare cose come ottenere "funzioni" dell'ora corrente ma qualcosa di simile a "procedure" (sebbene discutibile la soluzione di Haskell sia un'eccezione a questa).
singpolyma,

dal punto di vista di Haskell le "procedure" classiche (cose che hanno tipi come '... -> ()') sono in qualche modo banali in quanto una funzione pura con ... -> () non può fare nulla.
Carsten,

3
Il termine tipico di Haskell è "azione".
Sebastian Redl,

6
"Le monadi sono una sorta di punto e virgola sovraccaricabile." +1
user2805751

147

In Haskell si usa un costrutto chiamato monade per gestire gli effetti collaterali. Una monade sostanzialmente significa che incapsuli i valori in un contenitore e hai alcune funzioni per incatenare le funzioni da valori a valori all'interno di un contenitore. Se il nostro contenitore ha il tipo:

data IO a = IO (RealWorld -> (a,RealWorld))

possiamo implementare in sicurezza azioni IO. Questo tipo significa: un'azione di tipo IOè una funzione, che accetta un token di tipo RealWorlde restituisce un nuovo token, insieme a un risultato.

L'idea alla base di ciò è che ogni azione di I / O muta lo stato esterno, rappresentato dal token magico RealWorld. Usando le monadi, si possono concatenare più funzioni che mutano il mondo reale insieme. La funzione più importante di una monade è il legame>>= pronunciato :

(>>=) :: IO a -> (a -> IO b) -> IO b

>>=esegue un'azione e una funzione che prende il risultato di questa azione e ne crea una nuova. Il tipo restituito è la nuova azione. Ad esempio, supponiamo che esista una funzione now :: IO String, che restituisce una stringa che rappresenta l'ora corrente. Possiamo concatenarlo con la funzione putStrLnper stamparlo:

now >>= putStrLn

O scritto in do-Notation, che è più familiare a un programmatore imperativo:

do currTime <- now
   putStrLn currTime

Tutto ciò è puro, poiché mappiamo la mutazione e le informazioni sul mondo esterno al RealWorldtoken. Quindi ogni volta che esegui questa azione, ottieni ovviamente un output diverso, ma l'input non è lo stesso: il RealWorldtoken è diverso.


3
-1: non sono contento della RealWorldcortina fumogena. Tuttavia, la cosa più importante è come questo presunto oggetto viene trasmesso in una catena. Il pezzo mancante è dove inizia, dove si trova l'origine o la connessione al mondo reale - inizia con la funzione principale che gira nella monade IO.
u0b34a0f6ae,

2
@ kaizer.se Puoi pensare a un RealWorldoggetto globale che viene passato al programma all'avvio.
fuz,

6
Fondamentalmente, la tua mainfunzione accetta un RealWorldargomento. Viene eseguito solo dopo l'esecuzione.
Louis Wasserman,

13
Vedete, il motivo per cui nascondono RealWorlde forniscono solo funzioni scadenti per cambiarlo in questo modo putStrLn, è così che alcuni programmatori di Haskell non cambiano RealWorldcon uno dei loro programmi in modo tale che l'indirizzo e la data di nascita di Haskell Curry siano tali da diventare vicini di casa crescere (questo potrebbe danneggiare il continuum spazio-temporale in modo tale da danneggiare il linguaggio di programmazione di Haskell).
PyRulez

2
RealWorld -> (a, RealWorld) non si scompone come metafora anche in concomitanza, purché si tenga presente che il mondo reale potrebbe essere modificato da altre parti dell'universo al di fuori della propria funzione (o del proprio processo corrente) in ogni momento. Quindi (a) la metafora non abbattere, e (b) ogni volta che un valore che ha RealWorldcome suo tipo è passato a una funzione, la funzione deve essere rivalutato, perché il mondo reale sarà cambiato nel frattempo ( che è modellato come spiegato da @fuz, restituendo un diverso "valore simbolico" ogni volta che interagiamo con il mondo reale).
Qqwy

73

La maggior parte dei linguaggi di programmazione funzionale non sono puri, ovvero consentono alle funzioni non solo di dipendere dai loro valori. In quelle lingue è perfettamente possibile avere una funzione che ritorna l'ora corrente. Dalle lingue che hai taggato questa domanda con questo vale per Scala e F # (così come la maggior parte delle altre varianti di ML ).

In lingue come Haskell e Clean , che sono pure, la situazione è diversa. In Haskell l'ora corrente non sarebbe disponibile attraverso una funzione, ma una cosiddetta azione IO, che è il modo di Haskell di incapsulare gli effetti collaterali.

In Clean sarebbe una funzione, ma la funzione prenderebbe un valore mondiale come argomento e restituirebbe un nuovo valore mondiale (oltre al tempo corrente) come risultato. Il sistema di tipi si assicurerebbe che ogni valore mondiale possa essere usato una sola volta (e ogni funzione che consuma un valore mondiale ne produrrebbe uno nuovo). In questo modo la funzione time dovrebbe essere chiamata ogni volta con un argomento diverso e quindi dovrebbe essere possibile restituire una volta diversa ogni volta.


2
Questo fa sembrare che Haskell e Clean facciano cose diverse. Da quello che ho capito, fanno lo stesso, solo che Haskell offre una sintassi migliore (?) Per ottenere questo risultato.
Konrad Rudolph,

27
@Konrad: fanno la stessa cosa nel senso che entrambi usano le caratteristiche del sistema dei tipi per astrarre gli effetti collaterali, ma questo è tutto. Si noti che è molto buono spiegare la monade IO in termini di un tipo di mondo, ma lo standard Haskell in realtà non definisce un tipo di mondo e in realtà non è possibile ottenere un valore di tipo Mondo in Haskell (mentre è molto possibile e in effetti necessario in pulito). Inoltre Haskell non ha la tipizzazione univoca come funzionalità di sistema di tipo, quindi se ti dava accesso a un Mondo, non poteva assicurarti di usarlo in modo puro come fa Clean.
sepp2k,

51

"L'ora corrente" non è una funzione. È un parametro Se il tuo codice dipende dall'ora corrente, significa che il tuo codice è parametrizzato dall'ora.


22

Può assolutamente essere fatto in modo puramente funzionale. Esistono diversi modi per farlo, ma il più semplice è che la funzione time ritorni non solo sul tempo ma anche sulla funzione che devi chiamare per ottenere la misurazione del tempo successivo .

In C # potresti implementarlo in questo modo:

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

(Tieni presente che questo è un esempio pensato per essere semplice, non pratico. In particolare, i nodi dell'elenco non possono essere raccolti in modo errato perché sono radicati da ProgramStartTime.)

Questa classe "ClockStamp" si comporta come un elenco collegato immutabile, ma in realtà i nodi vengono generati su richiesta in modo che possano contenere il tempo "corrente". Qualsiasi funzione che vuole misurare il tempo dovrebbe avere un parametro 'clockStamp' e deve anche restituire la sua ultima misurazione dell'ora nel suo risultato (quindi il chiamante non vede le vecchie misurazioni), come questo:

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

Certo, è un po 'scomodo dover passare l'ultima misura dentro e fuori, dentro e fuori, dentro e fuori. Esistono molti modi per nascondere la caldaia, soprattutto a livello di progettazione del linguaggio. Penso che Haskell usi questo tipo di trucco e poi nasconda le parti brutte usando le monadi.


Interessante, ma quello i++nel ciclo for non è referenzialmente trasparente;)
snim2

@ snim2 Non sono perfetto. : P Prendi conforto nel fatto che la mutevolezza sporca non influisce sulla trasparenza referenziale del risultato. Se si passa lo stesso 'lastMeasurement' in due volte, si ottiene una misurazione successiva non aggiornata e si ottiene lo stesso risultato.
Craig Gidney,

@Strilanc Grazie per questo. Penso al codice imperativo, quindi è interessante vedere i concetti funzionali spiegati in questo modo. Posso quindi immaginare un linguaggio in cui questo naturale e sintatticamente più pulito.
WW.

In effetti, in C # potresti anche andare per la monade, evitando così il passaggio esplicito di timestamp. Hai bisogno di qualcosa del genere struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }. Ma il codice con questo non sarebbe ancora bello come Haskell con la dosintassi.
lasciato il

@leftaroundabout puoi fare finta di avere una monade in C # implementando la funzione bind come metodo chiamato SelectMany, che abilita la sintassi di comprensione della query. Tuttavia non puoi ancora programmare polimorficamente sulle monadi, quindi è tutta una battaglia in salita contro il sistema di tipo debole :(
sara,

16

Sono sorpreso che nessuna delle risposte o dei commenti menzioni coalgebre o coinduzione. Di solito, la coinduzione viene menzionata quando si ragiona su infinite strutture di dati, ma è anche applicabile a un flusso infinito di osservazioni, come un registro temporale su una CPU. Una coalgebra modella lo stato nascosto; e modelli di coinduzione osservando quello stato. ( Stato di costruzione dei modelli a induzione normale .)

Questo è un argomento caldo nella programmazione funzionale reattiva. Se sei interessato a questo genere di cose, leggi questo: http://digitalcommons.ohsu.edu/csetech/91/ (28 pagg.)


3
E che relazione c'è con questa domanda?
Nawaz,

5
La tua domanda riguardava la modellazione del comportamento dipendente dal tempo in modo puramente funzionale, ad esempio una funzione che restituisce l'orologio di sistema corrente. Puoi infilare qualcosa di equivalente a una monade IO attraverso tutte le funzioni e il loro albero delle dipendenze per ottenere l'accesso a quello stato; oppure puoi modellare lo stato definendo le regole di osservazione anziché le regole costruttive. Questo è il motivo per cui modellare induttivamente uno stato complesso nella programmazione funzionale sembra così innaturale, perché lo stato nascosto è davvero una proprietà coinduttiva .
Jeffrey Aguilera,

Ottima fonte! C'è qualcosa di più recente? La comunità JS sembra ancora alle prese con le astrazioni dei dati di flusso.
Dmitri Zaitsev

12

Sì, è possibile che una funzione pura restituisca il tempo, se viene assegnato quel tempo come parametro. Argomento temporale diverso, risultato temporale diverso. Quindi forma anche altre funzioni del tempo e combinale con un semplice vocabolario di funzioni (-di-tempo) -trasformanti (di ordine superiore). Poiché l'approccio è apolide, il tempo qui può essere continuo (indipendente dalla risoluzione) piuttosto che discreto, aumentando notevolmente la modularità . Questa intuizione è la base della programmazione reattiva funzionale (FRP).


11

Sì! Hai ragione! Now () o CurrentTime () o qualsiasi firma del metodo di tale sapore non mostra trasparenza referenziale in un modo. Ma per istruzione al compilatore è parametrizzato da un input di clock di sistema.

Per output, Now () potrebbe non seguire la trasparenza referenziale. Ma l'effettivo comportamento dell'orologio di sistema e la relativa funzione aderiscono alla trasparenza referenziale.


11

Sì, una funzione di acquisizione del tempo può esistere nella programmazione funzionale utilizzando una versione leggermente modificata della programmazione funzionale nota come programmazione funzionale impura (l'impostazione predefinita o principale è la pura programmazione funzionale).

In caso di ottenere il tempo (o leggere il file o lanciare missili) il codice deve interagire con il mondo esterno per portare a termine il lavoro e questo mondo esterno non si basa sulle basi pure della programmazione funzionale. Per consentire a un mondo di programmazione funzionale pura di interagire con questo impuro mondo esterno, le persone hanno introdotto una programmazione funzionale impura. Dopotutto, il software che non interagisce con il mondo esterno non è utile se non fare alcuni calcoli matematici.

Pochi linguaggi di programmazione di programmazione funzionale hanno questa caratteristica di impurità incorporata in loro in modo tale che non è facile separare quale codice è impuro e quale è puro (come F #, ecc.) E alcuni linguaggi di programmazione funzionale si assicurano che quando si fanno cose impure quel codice si distingue chiaramente rispetto al codice puro, come Haskell.

Un altro modo interessante di vedere questo sarebbe che la tua funzione get time nella programmazione funzionale prenderebbe un oggetto "mondo" che ha lo stato attuale del mondo come il tempo, il numero di persone che vivono nel mondo, ecc. Quindi ottenere il tempo da quale mondo l'oggetto sarebbe sempre puro, cioè passerai nello stesso stato mondiale e otterrai sempre lo stesso tempo.


1
"Dopo tutto un software che non interagisce con il mondo esterno non è utile se non fare alcuni calcoli matematici." A quanto ho capito, anche in questo caso l'input per i calcoli sarebbe codificato nel programma, anche non molto utile. Non appena si desidera leggere i dati di input per il calcolo matematico dal file o dal terminale, è necessario un codice impuro.
Giorgio,

1
@Ankur: Questa è la stessa cosa esatta. Se il programma interagisce con qualcos'altro oltre a se stesso (ad esempio il mondo attraverso la tastiera, per così dire) è ancora impuro.
identità

1
@Ankur: Sì, penso che tu abbia ragione! Anche se potrebbe non essere molto pratico passare grandi dati di input sulla riga di comando, questo può essere un modo puro di farlo.
Giorgio,

2
Avere "l'oggetto del mondo" incluso il numero di persone che vivono nel mondo porta il computer in esecuzione a un livello quasi onnisciente. Penso che il caso normale sia che include cose come quanti file sono sul tuo HD e qual è la home directory dell'utente corrente.
ziggystar,

4
@ziggystar - l '"oggetto del mondo" in realtà non include nulla - è semplicemente un proxy per il cambiamento dello stato del mondo al di fuori del programma. Il suo unico scopo è quello di contrassegnare esplicitamente lo stato mutabile in modo che il sistema di tipi possa identificarlo.
Kris Nuttycombe,

7

La tua domanda unisce due misure correlate di un linguaggio informatico: funzionale / imperativo e puro / impuro.

Un linguaggio funzionale definisce le relazioni tra input e output delle funzioni e un linguaggio imperativo descrive operazioni specifiche in un ordine specifico da eseguire.

Un linguaggio puro non crea né dipende da effetti collaterali e un linguaggio impuro li usa dappertutto.

I programmi puri al cento per cento sono sostanzialmente inutili. Possono eseguire un calcolo interessante, ma poiché non possono avere effetti collaterali non hanno input o output, quindi non sapresti mai cosa hanno calcolato.

Per essere utile a tutti, un programma deve essere almeno un impuro di fumo. Un modo per rendere utile un programma puro è quello di inserirlo in un involucro impuro sottile. Come questo programma Haskell non testato:

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print

4
Sarebbe utile se si potesse affrontare il problema specifico di ottenere il tempo e spiegare un po 'fino a che punto consideriamo IOvalori e risultati puri.
AndrewC,

In effetti, anche i programmi puri al 100% riscaldano la CPU, il che è un effetto collaterale.
Jörg W Mittag,

3

Stai affrontando un argomento molto importante nella programmazione funzionale, ovvero eseguendo I / O. Il modo in cui molti linguaggi puri agiscono su questo è usando linguaggi specifici del dominio incorporato, ad esempio un sublingua il cui compito è codificare azioni , che possono avere risultati.

Il runtime di Haskell, ad esempio, si aspetta che io definisca un'azione chiamata maincomposta da tutte le azioni che compongono il mio programma. Il runtime quindi esegue questa azione. Il più delle volte, nel farlo, esegue il codice puro. Di volta in volta il runtime utilizzerà i dati calcolati per eseguire l'I / O e restituire i dati in codice puro.

Potresti lamentarti che questo suona come un imbroglio, e in un certo senso lo è: definendo le azioni e aspettandosi che il runtime le esegua, il programmatore può fare tutto ciò che un normale programma può fare. Ma il forte sistema di tipi di Haskell crea una forte barriera tra parti pure e "impure" del programma: non puoi semplicemente aggiungere, diciamo, due secondi al tempo corrente della CPU, e stamparlo, devi definire un'azione che si traduca nella corrente Tempo CPU e passa il risultato a un'altra azione che aggiunge due secondi e stampa il risultato. Scrivere troppo programma è considerato cattivo stile, perché rende difficile dedurre quali effetti sono causati, rispetto ai tipi di Haskell che ci dicono tutto ciò che possiamo sapere su un valore.

Esempio: clock_t c = time(NULL); printf("%d\n", c + 2);in C, contro main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)in Haskell. L'operatore >>=viene utilizzato per comporre azioni, passando il risultato del primo a una funzione risultante nella seconda azione. Apparentemente abbastanza arcani, i compilatori Haskell supportano lo zucchero sintattico che ci consente di scrivere quest'ultimo codice come segue:

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

Quest'ultimo sembra abbastanza imperativo, vero?


1

Se sì, come può esistere? Non viola il principio della programmazione funzionale? In particolare, viola la trasparenza referenziale

Non esiste in senso puramente funzionale.

O se no, come si può sapere l'ora corrente nella programmazione funzionale?

In primo luogo può essere utile sapere come viene recuperata l'ora su un computer. Fondamentalmente ci sono circuiti a bordo che tengono traccia del tempo (che è la ragione per cui un computer di solito avrebbe bisogno di una batteria a piccole celle). Quindi potrebbe esserci un processo interno che imposta il valore del tempo in un determinato registro di memoria. Che essenzialmente si riduce a un valore che può essere recuperato dalla CPU.


Per Haskell esiste un concetto di "azione IO" che rappresenta un tipo che può essere creato per eseguire alcuni processi IO. Quindi, invece di fare riferimento a un timevalore, facciamo riferimento a un IO Timevalore. Tutto ciò sarebbe puramente funzionale. Non stiamo facendo riferimento timema qualcosa sulla falsariga di "leggere il valore del registro temporale" .

Quando eseguiamo effettivamente il programma Haskell, l'azione IO verrebbe effettivamente eseguita.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.