2009-09-18 6 views
13

So che NULL non è necessario in un linguaggio di programmazione e di recente ho preso la decisione di non includere NULL nel mio linguaggio di programmazione. La dichiarazione viene eseguita tramite l'inizializzazione, quindi è impossibile avere una variabile non inizializzata. La mia speranza è che questo eliminerà lo NullPointerException in favore di eccezioni più significative o semplicemente non avendo certi tipi di bug.Implicazioni di non includere NULL in una lingua?

Ovviamente, poiché la lingua è implementata in C, saranno utilizzati i NULL sotto le copertine.

La mia domanda è, oltre a usare NULL come flag di errore (questo è gestito con eccezioni) o come endpoint per strutture di dati come liste collegate e alberi binari (questo è gestito con sindacati discriminati) ci sono altri usi- casi per NULL per i quali dovrei avere una soluzione? Ci sono implicazioni davvero importanti nel non avere NULL che potrebbe causarmi problemi?

+0

possibile duplicato di [tipi di riferimento non annullabili] (http://stackoverflow.com/questions/693325/non-nullable-reference- typeses), [about-the-non-nullable-types-debate] (http : //stackoverflow.com/questions/641328/about-the-non-nullable-types-debate) – nawfal

risposta

8

C'è un recente articolo fa riferimento in LTU da Tony Hoare intitolato Null References: The Billion Dollar Mistake che descrive un metodo per consentire la presenza di NULL in un linguaggio di programmazione, ma anche elimina il rischio di fare riferimento a un tale riferimento NULL. Sembra così semplice ma è un'idea così potente.

Aggiornamento: ecco un link alla carta reale che ho letto, che parla l'implementazione in Eiffel: http://docs.eiffel.com/book/papers/void-safety-how-eiffel-removes-null-pointer-dereferencing

+0

Ho letto quell'articolo su λtU ma ho incompreso originariamente il suo significato. Grazie per avermi corretto su questo. Leggerò su come Eiffel fa questo. – Imagist

1

Quello che viene subito in mente è pass-by-di riferimento parametri. Sono principalmente un codificatore Objective-C, quindi sono abituato a vedere le cose un po 'come questo:

NSError *error; 
[anObject doSomething:anArgumentObject error:&error]; 
// Error-handling code follows...

Dopo questo codice viene eseguito, l'oggetto error ha dettagli circa l'errore che si è verificato, se presente. Ma dico che non importa se si verifica un errore:

[anObject doSomething:anArgumentObject error:nil];

Dal momento che non passo in alcun valore effettivo per l'oggetto errore, ottengo alcun risultato indietro, e non mi preoccupo per il parsing di un errore (dal momento che non mi interessa in primo luogo se si verifica).

Hai già detto che stai gestendo gli errori in un modo diverso, quindi questo specifico esempio non si applica in realtà, ma il punto è valido: cosa fai quando passi qualcosa per riferimento? O la tua lingua non lo fa?

+0

Non passo le cose indietro per riferimento; tutto è pass-by-const-reference. Tuttavia, sarei interessato a capire cosa fa il codice sopra. Non ho mai codificato Objective-C prima quindi non so cosa sta succedendo in quel codice. Sareste disposti a dare una breve spiegazione? – Imagist

+0

Sicuro. Fondamentalmente, gli errori in Objective-C che non giustificano le eccezioni sono rappresentati da istanze dell'oggetto NSError. Molti metodi potrebbero fallire durante la loro esecuzione, ma non necessariamente; quello che fanno in questo caso è aggiungere un argomento di riferimento che è un oggetto NSError, quindi per capire l'errore il programmatore crea uno di quegli oggetti e lo passa facendo riferimento al metodo. Se il metodo incontra un problema, inserisce i dettagli nell'oggetto e il programmatore è responsabile della sua successiva gestione. Se al programmatore non interessa l'errore, però, possono passare in nil. – Tim

0

Nella mia mente ci sono due usi casi per i quali NULL viene generalmente utilizzati:

  • La variabile in questione non hanno un valore (Niente)
  • Noi non conosciamo il valore del variabile in questione (Sconosciuto)

Entrambe le ricorrenze comuni e, onestamente, l'utilizzo di NULL per entrambi può causare confusione.

Vale la pena notare che alcune lingue che non supportano NULL supportano il nulla di Nothing/Unknown. Haskell, ad esempio, supporta "Forse", che può contenere un valore o Nothing. Pertanto, i comandi possono restituire (e accettare) un tipo che sanno avranno sempre un valore, oppure possono restituire/accettare "Forse" per indicare che potrebbe non esserci un valore.

+0

Javascript fa questa distinzione tra null e undefined. – ndp

0

Preferisco il concetto di avere i puntatori non annullabili come predefiniti, con possibilità di puntatori null. Si può quasi fare questo con C++ attraverso i riferimenti (&) piuttosto che i puntatori, ma in alcuni casi può diventare piuttosto nodoso e fastidioso.

Un linguaggio può fare senza null nel senso Java/C, ad esempio Haskell (e la maggior parte degli altri linguaggi funzionali) ha un tipo "Forse" che è effettivamente un costrutto che fornisce solo il concetto di un puntatore nullo opzionale.

3

Prendendo in prestito una pagina da Haskell's Maybe monad, come gestirai il caso di un valore restituito che può o non può esistere? Ad esempio, se si è tentato di allocare memoria ma nessuno era disponibile. O forse hai creato un array per contenere 50 foos, ma nessuno dei foos è stato istanziato ancora - hai bisogno di un modo per essere in grado di controllare questo tipo di cose.

Credo che si può utilizzare le eccezioni per coprire tutti questi casi, ma vuol dire che un programmatore dovrà avvolgere tutte di coloro che in un blocco try-catch? Sarebbe fastidioso al massimo. O tutto dovrebbe restituire il proprio valore più un valore booleano che indica se il valore è valido, il che non è certamente migliore.

FWIW, io non sono a conoscenza di qualsiasi programma che non ha alcuni sorta di nozione di NULL - hai null in tutte le lingue in stile C e Java; Python ha None, Scheme, Lisp, Smalltalk, Lua, Ruby tutti hanno nil; VB utilizza Nothing; e Haskell ha un diverso tipo di nothing.

Ciò non significa una lingua assolutamente deve avere un qualche tipo di null, ma se tutti gli altri grandi linguaggi là fuori lo usano, sicuramente c'era un buon ragionamento dietro di esso.

D'altra parte, se si sta solo creando un DSL leggero o qualche altra lingua non generale, è probabile che si ottenga senza null se nessuno dei tuoi tipi di dati nativi lo richiede.

+0

Le eccezioni sono deselezionate, quindi se è possibile garantire che non si verifichi una situazione, non è necessario provare/catturare. – Imagist

+0

Tecnicamente non si può mai "assicurare" che una situazione non si verifichi, a meno che non si abbia il controllo totale su tutti gli aspetti dell'hardware e del software in questione. –

+0

@Chris Vero, ma c'è una ragionevole sicurezza. Le letture da file, connessioni di rete e input dell'utente generalmente non possono essere garantite. Tuttavia, se hai qualcosa come 'x/2', puoi essere ragionevolmente sicuro che non genererà un' DivisionByZeroException'. – Imagist

1

Penso che sia utile per un metodo restituire NULL - ad esempio per un metodo di ricerca che dovrebbe restituire un oggetto, può restituire l'oggetto trovato o NULL se non è stato trovato.

Sto iniziando a imparare Ruby e Ruby ha un concetto molto interessante per NULL, forse potresti prendere in considerazione l'implementazione di qualcosa di silimar. In Ruby, NULL è chiamato Nil, ed è un oggetto reale proprio come qualsiasi altro oggetto. Capita di essere implementato come un oggetto Singleton globale. Anche in Ruby, c'è un oggetto False, e sia Nil che False valutano come falso nelle espressioni booleane, mentre tutto il resto viene valutato come true (anche 0, ad esempio, restituisce true).

+0

Se un metodo non riesce nel suo lavoro, dovrebbe generare un'eccezione. – Imagist

+0

Un certo numero di persone non sono fan delle eccezioni. Joel ha un intero post sul blog che si sta trasformando nella nuova citazione regex di Jamie Zawinski su questo sito. –

+0

@Chris Link? Inoltre, vale la pena notare che lingue diverse usano eccezioni in modo molto diverso. In C++ ci sono pericoli di perdita di memoria con eccezioni, e in Java, le eccezioni controllate vengono utilizzate in modo improprio il più delle volte. Python, tuttavia, utilizza le eccezioni in modo molto efficace, al punto che di solito è meglio cercare di rilevare errori piuttosto che verificare preventivamente eventuali errori. – Imagist

0

Non mi è chiaro il motivo per cui vorreste eliminare il concetto di "null" da una lingua. Cosa faresti se la tua app ti chiedesse di eseguire un'inizializzazione "pigramente", ovvero, non eseguirai l'operazione finché i dati non saranno necessari? Es:

public class ImLazy { 
public ImLazy() { 
    //I can't initialize resources in my constructor, because I'm lazy. 
    //Maybe I don't have a network connection available yet, or maybe I'm 
    //just not motivated enough. 
} 

private ResourceObject lazyObject; 
public ResourceObject getLazyObject() { //initialize then return 
    if (lazyObject == null) { 
    lazyObject = new DatabaseNetworkResourceThatTakesForeverToLoad(); 
    } 
} 

public ResourceObject isObjectLoaded() { //just return the object 
    return (lazyObject != null); 
} 
} 

In un caso come questo, come è possibile restituire un valore per getObject()? Potremmo inventare una di queste due cose:

- Richiedere all'utente di inizializzare LazyObject nella dichiarazione. L'utente dovrà quindi compilare un oggetto fittizio (UselessResourceObject), che richiede loro di scrivere tutto lo stesso codice di controllo degli errori (if (lazyObject.equals (UselessResourceObject) ...) O:

Vieni con un altro valore, che funziona lo stesso di nulla, ma ha un nome diverso

Per qualsiasi linguaggio complesso/OO avete bisogno di questa funzionalità, o qualcosa di simile, per quanto Posso vedere. Potrebbe essere utile avere un tipo di riferimento non nullo (ad esempio, in una firma del metodo, in modo da non dover eseguire un controllo nullo nel codice del metodo), ma la funzionalità nulla dovrebbe essere disponibile per i casi in cui usarlo

+0

La mia lingua ha il supporto integrato per i thunk. In questo caso il costruttore 'ImLazy()' inizializzerà 'lazyObject = new Thunk (new DatabaseNetworkResourceThatTakesForeverToLoad());' (approssimativamente, la sintassi della lingua è diversa). In questo modo, anche quando viene chiamato getLazyObject(), l'oggetto non viene inizializzato (viene inizializzato quando si utilizza qualcosa che causerebbe un effetto collaterale). Se vuoi caricare l'oggetto prima, puoi chiamare 'lazyObject.resolve()' per risolvere il thunk.Sto anche considerando di avere un thread integrato che usa i cicli di inattività per risolvere presto i thunk. – Imagist

+0

Sembra che abbia senso. Se hai un costruttore con argomenti, immagino che tu memorizzeresti gli argomenti passati finché il Thunk non inizializza l'oggetto? – RMorrisey

+0

Giusto. In realtà non si passa l'oggetto, si passa il costruttore e gli argomenti al costruttore. – Imagist

0

Interessante discussione in corso qui.

Se stavo costruendo un linguaggio, non so davvero se avrei il concetto di null. Immagino dipenda da come voglio che il linguaggio appaia. Caso in questione: ho scritto un semplice linguaggio di template il cui principale punto di forza sono i token nidificati e la facilità di creare un token come un elenco di valori. Non ha il concetto di null, ma in realtà non ha il concetto di nessun tipo oltre alla stringa.

In confronto, il linguaggio che è incorporato, Icona, utilizza nettamente estesamente. Probabilmente la cosa migliore che i designer di linguaggio per Icon hanno fatto con null è renderla sinonimo di una variabile non inizializzata (cioè non si può sapere la differenza tra una variabile che non esiste e una che al momento detiene il valore null). E quindi creato due operatori di prefissi per controllare null e not-null.

In PHP, a volte utilizzo null come valore "terzo" booleano. Ciò è valido nelle classi di tipo "black-box" (ad esempio, il nucleo ORM) in cui uno stato può essere True, False o I Do Not Know. Null è utilizzato per il terzo valore.

Naturalmente, entrambi questi linguaggi non hanno puntatori nello stesso modo in cui lo fa C, quindi i puntatori nulli non esistono.

0

Utilizziamo sempre valori nulli nella nostra applicazione per rappresentare il caso "niente". Ad esempio, se ti viene chiesto di cercare alcuni dati nel database dato un id, e nessun record corrisponde a quell'id: return null. Questo è molto utile perché possiamo memorizzare valori nulli nella nostra cache, il che significa che non dobbiamo tornare al database se qualcuno chiede di nuovo quell'id in pochi secondi.

La cache stessa ha due diversi tipi di risposte: null, ovvero non c'era alcuna voce nella cache o un oggetto entry. L'oggetto entry potrebbe avere un valore nullo, come nel caso in cui abbiamo memorizzato nella cache una ricerca db null.

La nostra app è scritta in Java, ma anche con eccezioni non controllate farlo con eccezioni sarebbe incredibilmente fastidioso.

+0

I valori null del database e del linguaggio sono concettualmente diversi (esiste in realtà una distinzione di tipo eseguita in C#). – Imagist

+0

@Imagist: Non sto parlando di null del database, sto parlando dello scenario senza record. Per un metodo che restituisce un singolo oggetto, è necessario un modo per indicare che nessun oggetto può essere restituito. Un'eccezione può farlo, ma è molto complicato programmarlo in questo modo, almeno in Java. Forse la tua lingua ha un approccio elegante che rende più facile. Ma nella mia esperienza null è molto utile. –

0

Se si accetta la proposizione che i potenti linguaggi debbano avere una sorta di puntatore o tipo di riferimento (cioè qualcosa che può contenere un riferimento a dati che non esiste in fase di compilazione), e qualche forma di tipo di matrice (o altri mezzi di avere una collezione di slot di memoria che sono sequenzialmente indirizzabili tramite indice intero), e che gli slot di quest'ultimo dovrebbero essere in grado di contenere il primo, e si accetta la possibilità che si debba leggere alcuni slot di un array di puntatori/riferimenti prima che esistano valori sensibili per tutti loro, allora ci saranno programmi che, dal punto di vista del compilatore, leggeranno uno slot di array prima che sia stato scritto un valore ragionevole (cercando di accertare nel caso generale se uno slot dell'array potrebbe essere letto prima che sia scritto sarebbe equivalente al problema dell'arresto).

Mentre per una lingua è possibile che tutti gli slot di matrice vengano inizializzati con alcuni riferimenti non nulli prima che uno di essi possa essere letto, in molte situazioni non c'è davvero nulla che possa essere memorizzato quale sarebbe meglio di null: se viene fatto un tentativo di leggere uno slot non ancora scritto e dereferenziazione l'elemento (non) contenuto lì, rappresenta un errore, e sarebbe meglio che il sistema intrappoli quella condizione piuttosto che accedere ad alcuni oggetto arbitrario il cui unico scopo per l'esistenza è di dare agli slot dell'array qualcosa di non nulla a cui possono fare riferimento.

Problemi correlati