Perché la maggior parte dei "ben noti" linguaggi imperativi / OO consente l'accesso non controllato ai tipi che possono rappresentare un valore "nulla"?

29

Ho letto della (non) comodità di avere null invece di (per esempio) Maybe . Dopo aver letto questo articolo Sono convinto che sarebbe molto meglio usare Maybe (o qualcosa di simile). Tuttavia, sono sorpreso nel vedere che tutti i linguaggi di programmazione "ben noti" imperativi o orientati agli oggetti utilizzano ancora null (che consente l'accesso non controllato ai tipi che possono rappresentare un valore "nulla") e che Maybe è principalmente usato nei linguaggi di programmazione funzionale.

Ad esempio, guarda il seguente codice C #:

void doSomething(string username)
{
    // Check that username is not null
    // Do something
}

Qualcosa ha un cattivo odore qui ... Perché dovremmo controllare se l'argomento è nullo? Non dovremmo assumere che ogni variabile contenga un riferimento a un oggetto? Come puoi vedere, il problema è che, per definizione, quasi tutte le variabili possono contenere un riferimento null. E se potessimo decidere quali variabili sono "nullable" e quali no? Ciò ci farebbe risparmiare un sacco di sforzi durante il debugging e alla ricerca di una "NullReferenceException". Immagina che, per impostazione predefinita, nessun tipo possa contenere un riferimento null . Invece, dovresti dichiarare esplicitamente che una variabile può contenere un riferimento null , solo se ne hai davvero bisogno. Questa è l'idea alla base di Maybe. Se hai una funzione che in alcuni casi fallisce (ad esempio una divisione per zero), puoi restituire un Maybe<int> , dichiarando esplicitamente che il risultato potrebbe essere un int, ma anche nulla! Questo è un motivo per preferire Forse anziché null. Se sei interessato ad altri esempi, ti suggerisco di leggere questo articolo .

I fatti sono che, nonostante gli svantaggi di rendere la maggior parte dei tipi nullable per impostazione predefinita, la maggior parte dei linguaggi di programmazione OO lo fanno effettivamente. Ecco perché mi chiedo:

  • Che tipo di argomenti avresti per implementare null nel tuo linguaggio di programmazione anziché Maybe ? Ci sono dei motivi o è solo un "bagaglio storico"?

Assicurati di comprendere la differenza tra null e Maybe prima di rispondere a questa domanda.

    
posta aochagavia 15.12.2013 - 15:36
fonte

9 risposte

15

Credo che sia principalmente il bagaglio storico.

La lingua più importante e più vecchia con null è C e C ++. Ma qui, null ha senso. I puntatori sono ancora un concetto abbastanza numerico e di basso livello. E come ha detto qualcun altro, nella mentalità dei programmatori C e C ++, dover dire esplicitamente che il puntatore può essere null non ha senso.

Secondo in linea arriva Java. Considerando che gli sviluppatori Java stavano cercando di avvicinarsi al C ++, in modo che potessero semplificare la transizione da C ++ a Java, probabilmente non volevano fare confusione con questo concetto di base del linguaggio. Inoltre, implementare null esplicito richiederebbe molto più sforzo, perché devi verificare se il riferimento non nullo viene effettivamente impostato correttamente dopo l'inizializzazione.

Tutte le altre lingue sono uguali a Java. Di solito copiano il modo in cui C ++ o Java lo fanno, e considerando come il concetto di base di% implicito% co_de dei tipi di riferimento è, diventa davvero difficile progettare un linguaggio che usa esplicitamente null .

    
risposta data 15.12.2013 - 17:02
fonte
15

In realtà, null è una grande idea. Dato un puntatore, vogliamo designare che questo puntatore non fa riferimento a un valore valido. Quindi prendiamo una posizione di memoria, la dichiariamo non valida e ci atteniamo a quella convenzione (una convenzione a volte applicata con segfaults). Ora ogni volta che ho un puntatore posso controllare se contiene Nothing ( ptr == null ) o Some(value) ( ptr != null , value = *ptr ). Voglio che tu capisca che questo è equivalente a un tipo Maybe .

I problemi con questo sono:

  1. In molte lingue il sistema di tipi non aiuta qui a garantire un riferimento non nullo.

    Questo è un bagaglio storico, dal momento che molte lingue imperative o OOP mainstream hanno avuto solo miglioramenti incrementali nei loro sistemi di tipi rispetto ai predecessori. Piccoli cambiamenti hanno il vantaggio che le nuove lingue sono più facili da imparare. C # è un linguaggio mainstream che ha introdotto strumenti a livello di lingua per gestire meglio i null.

  2. I progettisti di API potrebbero restituire null in caso di errore, ma non un riferimento alla cosa vera in caso di successo. Spesso, la cosa (senza un riferimento) viene restituita direttamente. Questo appiattimento del livello di un puntatore rende impossibile l'utilizzo di null come valore.

    Questa è solo pigrizia da parte del progettista e non può essere aiutata senza imporre un corretto nidificazione con un sistema di tipi adeguato. Alcune persone potrebbero anche provare a giustificare ciò con considerazioni sulle prestazioni o con l'esistenza di controlli facoltativi (una raccolta potrebbe restituire null o l'elemento stesso, ma anche fornire un metodo contains ).

  3. In Haskell c'è una bella vista sul tipo Maybe come monade. Ciò semplifica la composizione delle trasformazioni sul valore contenuto.

    D'altra parte, i linguaggi di basso livello come C trattano a mala pena gli array come un tipo separato, quindi non sono sicuro di cosa ci aspettiamo. Nei linguaggi OOP con polimorfismo parametrizzato, un tipo Maybe controllato in fase di esecuzione è piuttosto semplice da implementare.

risposta data 15.12.2013 - 16:34
fonte
9

La mia comprensione è che null era un costrutto necessario per astrarre i linguaggi di programmazione dall'assemblaggio. 1 I programmatori avevano bisogno di indicare che un puntatore o un valore di registro era not a valid value e null è diventato il termine comune per quel significato.

Rafforzare il punto che null è solo una convenzione per rappresentare un concetto, il valore effettivo per null utilizzato per essere in grado di / può variare in base al linguaggio di programmazione e alla piattaforma.

Se stavi progettando una nuova lingua e volessi evitare null ma invece usare maybe , allora incoraggerei un termine più descrittivo come not a valid value o navv per indicare l'intento. Ma il nome di quel non valore è un concetto separato dal fatto che dovresti consentire non-valori di esistere anche nella tua lingua.

Prima di poter decidere su uno di questi due punti, è necessario definire qual è il significato di maybe per il sistema. Potresti scoprire che è solo un rinominare il significato di null di not a valid value o potresti trovare una semantica diversa per la tua lingua.

Allo stesso modo, la decisione se controllare l'accesso con null di accesso o riferimento è un'altra decisione progettuale della tua lingua.

Per fornire un po 'di storia, C aveva un'ipotesi implicita che i programmatori capissero cosa stavano tentando di fare quando manipolavano la memoria. Poiché si trattava di un'astrazione superiore all'assemblaggio e delle lingue imperative che l'avevano preceduta, mi sarei arrischiato a pensare che il pensiero di salvaguardare il programmatore da un riferimento errato non fosse passato per la testa.

Credo che alcuni compilatori o i loro strumenti aggiuntivi possano fornire una misura di controllo contro l'accesso al puntatore non valido. Così altri hanno notato questo potenziale problema e preso misure per proteggerlo.

La possibilità o meno di permetterlo dipende da ciò che si desidera che il linguaggio venga realizzato e dal livello di responsabilità che si desidera trasmettere agli utenti della propria lingua. Dipende anche dalla tua capacità di creare un compilatore per limitare questo tipo di comportamento.

Quindi per rispondere alle tue domande:

  1. "Che tipo di argomenti ..." - Beh, dipende da cosa vuoi che faccia il linguaggio. Se vuoi simulare l'accesso bare metal, puoi volerlo permettere.

  2. "è solo un bagaglio storico?" Forse, forse no. null ha certamente avuto / ha un significato per un certo numero di lingue e aiuta a guidare l'espressione di quelle lingue. Il precedente storico potrebbe aver influenzato le lingue più recenti e il loro rilascio di null , ma è un po 'troppo per sventolare le mani e dichiarare il concetto di bagaglio storico inutile.

1 Vedi questo articolo di Wikipedia nonostante il credito sia concesso a Hoare per null valori e linguaggi orientati agli oggetti. Credo che le lingue imperative siano progredite lungo un albero genealogico diverso da quello di Algol.

    
risposta data 15.12.2013 - 16:07
fonte
7

Se guardi gli esempi nell'articolo che hai citato, il più delle volte usando Maybe non accorcia il codice. Non elimina la necessità di controllare Nothing . L'unica differenza è che ti ricorda di farlo tramite il sistema di tipi.

Nota, io dico "ricorda", non forza. I programmatori sono pigri. Se un programmatore è convinto che un valore non può essere Nothing , sarà possibile dereferenziare Maybe senza verificarlo, proprio come se ora denitassero un puntatore nullo. Il risultato finale è la conversione di un'eccezione del puntatore nullo in un'eccezione "dereferenziato vuoto".

Lo stesso principio della natura umana si applica ad altre aree in cui i linguaggi di programmazione cercano di forzare i programmatori a fare qualcosa. Ad esempio, i progettisti Java hanno tentato di forzare le persone a gestire la maggior parte delle eccezioni, il che ha comportato un elevato numero di combinazioni che ignorano in silenzio o propagano ciecamente le eccezioni.

Ciò che rende bello Maybe è quando vengono prese molte decisioni tramite la corrispondenza dei pattern e il polimorfismo invece dei controlli espliciti. Ad esempio, potresti creare funzioni separate processData(Some<T>) e processData(Nothing<T>) , che non puoi fare con null . Spostate automaticamente la gestione degli errori in una funzione separata, che è molto auspicabile nella programmazione funzionale in cui le funzioni vengono passate e valutate pigramente anziché essere sempre chiamate in un modo top-down. In OOP, il modo migliore per disaccoppiare il codice di gestione degli errori è con le eccezioni.

    
risposta data 15.12.2013 - 22:53
fonte
1

Maybe è un modo molto funzionale di pensare a un problema: c'è una cosa e può o non può avere un valore definito. In un senso orientato agli oggetti, tuttavia, sostituiamo l'idea di una cosa (non importa se ha un valore o meno) con un oggetto. Chiaramente, un oggetto ha un valore. In caso contrario, diciamo che l'oggetto è null , ma ciò che intendiamo veramente è che non esiste alcun oggetto. Il riferimento che abbiamo all'oggetto non indica nulla. Tradurre Maybe in un concetto OO non fa nulla di nuovo, anzi, rende solo un codice molto più ingombrante. Devi ancora avere un riferimento null per il valore di Maybe<T> . Devi ancora fare controlli nulli (in effetti devi fare molti più assegni nulli, ingombrare il tuo codice), anche se ora sono chiamati "forse assegni". Certo, scriverò codice più robusto come sostiene l'autore, ma direi che è solo perché hai reso il linguaggio molto più astratto e ottuso, richiedendo un livello di lavoro che non è necessario nella maggior parte dei casi. Prenderò volentieri una NullReferenceException di tanto in tanto che gestirò codice spaghetti eseguendo un controllo Maybe ogni volta che accedo a una nuova variabile.

    
risposta data 15.12.2013 - 16:34
fonte
1

Il concetto di null può essere facilmente ricondotto a C ma non è questo il problema.

Il mio linguaggio quotidiano di scelta è C # e manterrò null con una differenza. C # ha due tipi di tipi, valori e riferimenti . I valori non possono mai essere null , ma ci sono momenti in cui mi piacerebbe essere in grado di esprimere che nessun valore è perfetto. Per fare ciò, C # utilizza Nullable types quindi int sarebbe il valore e int? il valore nullable. Questo è il modo in cui penso che anche i tipi di riferimento dovrebbero funzionare.

Vedi anche: riferimento Null potrebbe non essere un errore :

Null references are helpful and sometimes indispensable (consider how much trouble if you may or may not return a string in C++). The mistake really is not in the existence of the null pointers, but in how the type system handles them. Unfortunately most languages (C++, Java, C#) don’t handle them correctly.

    
risposta data 18.02.2014 - 07:40
fonte
0

Penso che questo sia dovuto al fatto che la programmazione funzionale è molto preoccupata per i tipi , specialmente i tipi che combinano altri tipi (tuple, funzioni come tipi di prima classe, monadi, ecc.) rispetto alla programmazione orientata agli oggetti ( o almeno inizialmente lo fece).

Le versioni moderne dei linguaggi di programmazione di cui stai parlando (C ++, C #, Java) sono tutte basate su linguaggi che non avevano alcuna forma di programmazione generica (C, C # 1.0, Java 1). Senza di ciò, puoi ancora creare una sorta di differenza tra oggetti nullable e non annullabili nel linguaggio (come i riferimenti C ++, che non possono essere null , ma sono anche limitati), ma è molto meno naturale.

    
risposta data 15.12.2013 - 17:20
fonte
0

Penso che la ragione fondamentale sia che sono necessari pochi controlli nulli per rendere un programma "sicuro" contro la corruzione dei dati. Se un programma tenta di utilizzare il contenuto di un elemento dell'array o di un'altra posizione di memoria che si suppone sia stata scritta con un riferimento valido ma non lo sia, il risultato migliore è che venga generata un'eccezione. Idealmente, l'eccezione indicherà esattamente dove si è verificato il problema, ma ciò che conta è che qualche tipo di eccezione venga lanciata prima che il riferimento null venga memorizzato da qualche parte che potrebbe causare il danneggiamento dei dati. A meno che un metodo non memorizzi un oggetto senza tentare di usarlo prima, un tentativo di usare un oggetto, di per se stesso, costituisce un "controllo nullo" di sorta.

Se si vuole assicurare che un riferimento nullo che appare dove non dovrebbe causa un'eccezione particolare diversa da NullReferenceException , sarà spesso necessario includere controlli nulli ovunque. D'altra parte, il semplice fatto di assicurare che si verifichi qualche eccezione prima che un riferimento nullo possa causare "danno" oltre a quello già fatto richiederà spesso relativamente pochi test - il test sarebbe generalmente richiesto solo nei casi in cui un oggetto memorizzerebbe un riferimento senza cercare di usarlo, e il riferimento null sovrascriverebbe uno valido, o farebbe sì che altri codici interpretino erroneamente altri aspetti dello stato del programma. Tali situazioni esistono, ma non sono così comuni; i riferimenti null più accidentali verranno catturati molto rapidamente indipendentemente dal fatto che vengano controllati o meno .

    
risposta data 28.01.2014 - 19:44
fonte
0

" Maybe ", come scritto, è un costrutto di livello superiore rispetto a null. Usando più parole per definirlo, Forse è "un puntatore a una cosa o un puntatore a nulla, ma il compilatore non ha ancora ricevuto sufficienti informazioni per determinare quale." Questo ti costringe a controllare in modo esplicito ogni valore, a meno che tu non costruisca una specifica per il compilatore che sia abbastanza intelligente da tenere il passo con il codice che scrivi.

Puoi realizzare un'implementazione di Maybe con un linguaggio che ha i null facilmente. Il C ++ ne ha uno sotto forma di boost::optional<T> . Rendere l'equivalente di null con Maybe è molto difficile. In particolare, se ho un Maybe<Just<T>> , non posso assegnarlo a null (perché un tale concetto non esiste), mentre un T** in una lingua con null è molto facile da assegnare a null. Questo impone di usare Maybe<Maybe<T>> , che è totalmente valido, ma ti costringerà a fare molti più controlli per usare quell'oggetto.

Alcuni linguaggi funzionali utilizzano Forse perché null richiede un comportamento non definito o una gestione delle eccezioni, nessuno dei quali è un concetto facile da mappare nelle sintassi del linguaggio funzionale. Forse riempie il ruolo molto meglio in tali situazioni funzionali, ma nei linguaggi procedurali, null è il re. Non è una questione di giusto e sbagliato, solo una questione di cosa rende più facile dire al computer di fare ciò che vuoi che faccia.

    
risposta data 18.02.2014 - 03:08
fonte