La semantica di Haskell utilizza un "valore inferiore" per analizzare il significato del codice Haskell. Non è qualcosa che usi davvero direttamente nella programmazione di Haskell e tornare None
non è affatto lo stesso tipo di cosa.
Il valore inferiore è il valore attribuito dalla semantica di Haskell a qualsiasi calcolo che non riesce a valutare normalmente un valore. Un modo in cui un calcolo di Haskell può farlo è in realtà lanciando un'eccezione! Quindi, se stavi provando a usare questo stile in Python, dovresti effettivamente generare eccezioni normalmente.
La semantica di Haskell utilizza il valore inferiore perché Haskell è pigro; sei in grado di manipolare "valori" restituiti da calcoli che non sono ancora stati effettivamente eseguiti. Puoi passarli a funzioni, inserirli in strutture di dati, ecc. Un calcolo così non valutato potrebbe generare un'eccezione o un ciclo per sempre, ma se non avessimo realmente bisogno di esaminare il valore, il calcolo non lo farà maieseguire e riscontrare l'errore e il nostro programma generale potrebbe riuscire a fare qualcosa di ben definito e finire. Quindi, senza voler spiegare cosa significhi il codice Haskell specificando l'esatto comportamento operativo del programma in fase di esecuzione, dichiariamo invece che tali calcoli errati producono il valore inferiore e spieghiamo cosa si comporta; in sostanza, qualsiasi espressione che deve dipendere da qualsiasi proprietà del valore inferiore (diverso da quello esistente) comporterà anche il valore inferiore.
Per rimanere "puri", tutti i modi possibili di generare il valore inferiore devono essere trattati come equivalenti. Ciò include il "valore inferiore" che rappresenta un ciclo infinito. Dato che non c'è modo di sapere che alcuni loop infiniti in realtà sono infiniti (potrebbero finire se li esegui per un po 'più a lungo), non puoi esaminare alcuna proprietà di un valore inferiore. Non puoi verificare se qualcosa è in basso, non puoi paragonarlo a nient'altro, non puoi convertirlo in una stringa, niente. Tutto quello che puoi fare con uno è metterlo (parametri di funzione, parte di una struttura di dati, ecc.) Intatto e non esaminato.
Python ha già questo tipo di fondo; è il "valore" che ottieni da un'espressione che genera un'eccezione o non termina. Poiché Python è rigoroso piuttosto che pigro, tali "fondi" non possono essere archiviati da nessuna parte e potenzialmente non vengono esaminati. Quindi non è necessario utilizzare il concetto del valore inferiore per spiegare come i calcoli che non riescono a restituire un valore possano ancora essere trattati come se avessero un valore. Ma non c'è nemmeno motivo per cui non potresti pensare in questo modo alle eccezioni se lo desideri.
Generare eccezioni è in realtà considerato "puro". E ' la cattura di eccezioni che le interruzioni purezza - proprio perché permette di ispezionare qualcosa su alcuni valori di fondo, invece di trattare tutti in modo intercambiabile. In Haskell puoi catturare solo eccezioni in IO
quanto consente un'interfaccia impura (quindi di solito accade a un livello abbastanza esterno). Python non impone la purezza, ma puoi ancora decidere tu stesso quali funzioni fanno parte del tuo "strato impuro esterno" piuttosto che funzioni pure, e permetti solo a te stesso di catturare eccezioni lì.
Il ritorno None
invece è completamente diverso. None
è un valore non inferiore; puoi verificare se qualcosa è uguale a esso, e il chiamante della funzione che ha restituito None
continuerà a funzionare, possibilmente usando in modo None
inappropriato.
Quindi, se stavi pensando di lanciare un'eccezione e vuoi "tornare in fondo" per emulare l'approccio di Haskell, non fai assolutamente nulla. Lascia che l'eccezione si propaghi. Questo è esattamente ciò che i programmatori Haskell intendono quando parlano di una funzione che restituisce un valore inferiore.
Ma questo non è ciò che i programmatori funzionali intendono quando dicono per evitare eccezioni. I programmatori funzionali preferiscono le "funzioni totali". Restituiscono sempre un valore non inferiore valido del loro tipo restituito per ogni possibile input. Quindi qualsiasi funzione che può generare un'eccezione non è una funzione totale.
Il motivo per cui ci piacciono le funzioni totali è che sono molto più facili da trattare come "scatole nere" quando le combiniamo e le manipoliamo. Se ho una funzione totale che restituisce qualcosa di tipo A e una funzione totale che accetta qualcosa di tipo A, allora posso chiamare il secondo sull'output del primo, senza sapere nulla sull'implementazione di nessuno dei due; So che otterrò un risultato valido, indipendentemente dal modo in cui il codice di entrambe le funzioni verrà aggiornato in futuro (purché venga mantenuta la loro totalità e finché manterranno la stessa firma di tipo). Questa separazione delle preoccupazioni può essere un aiuto estremamente potente per il refactoring.
È anche necessario per funzioni affidabili di ordine superiore (funzioni che manipolano altre funzioni). Se voglio scrivere il codice che riceve una funzione del tutto arbitrario (con un'interfaccia noto) come parametro io devo trattarlo come una scatola nera perché non ho modo di sapere quali ingressi potrebbero innescare un errore. Se mi viene data una funzione totale, nessun input provocherà un errore. Allo stesso modo il chiamante della mia funzione di ordine superiore non saprà esattamente quali argomenti utilizzare per chiamare la funzione che mi passa (a meno che non voglia dipendere dai miei dettagli di implementazione), quindi passare una funzione totale significa che non devono preoccuparsi cosa ci faccio.
Quindi un programmatore funzionale che ti consiglia di evitare le eccezioni preferirebbe che tu restituisse un valore che codifica l'errore o un valore valido e richiede che per usarlo sei pronto a gestire entrambe le possibilità. Cose come Either
tipi o Maybe
/ Option
tipi sono alcuni dei più semplici approcci di fare questo in lingue più fortemente tipizzati (di solito usati con la sintassi speciale o funzioni di ordine superiore per l'aiuto di colla cose insieme che hanno bisogno di una A
con le cose che producono una Maybe<A>
).
Una funzione che restituisce None
(se si è verificato un errore) o un valore (se non si è verificato un errore) non sta seguendo nessuna delle strategie precedenti.
In Python con duck digitando lo stile Oither / Maybe non viene utilizzato molto, invece si generano eccezioni, con i test per convalidare che il codice funzioni piuttosto che fidarsi delle funzioni per essere totali e automaticamente combinabili in base ai loro tipi. Python non ha alcuna possibilità di imporre che il codice usi cose come i tipi Forse correttamente; anche se lo stavi usando come una questione di disciplina, hai bisogno di test per esercitare effettivamente il tuo codice per convalidarlo. Quindi l'approccio eccezione / fondo è probabilmente più adatto alla pura programmazione funzionale in Python.