Ha senso avere sia il concetto di "null" che di "Forse"?


11

Durante la creazione di un client per un'API Web in C #, ho riscontrato un problema relativo nullal valore in cui rappresenterebbe due cose diverse:

  • niente , ad esempio un foomay o potrebbe non avere unbar
  • sconosciuto : per impostazione predefinita la risposta API include solo un sottoinsieme di proprietà, è necessario indicare quali proprietà aggiuntive si desidera. Così sconosciuto significa che la proprietà non è stata richiesta dall'API.

Dopo alcune ricerche ho scoperto il tipo Maybe (o Opzione), come viene utilizzato nei linguaggi funzionali e come "risolve" i problemi di dereferenziazione nulla costringendo l'utente a pensare alla possibile assenza di un valore. Tuttavia, tutte le risorse che ho incontrato parlavano di sostituire null con Forse . Ho trovato alcune menzioni della logica a tre valori , ma non la capisco del tutto e la maggior parte delle volte la sua menzione era nel contesto di "è una brutta cosa".

Ora mi chiedo se abbia senso avere sia il concetto di null che Forse , per rappresentare rispettivamente lo sconosciuto e il nulla . È questa la logica a tre valori di cui ho letto o ha un altro nome? O è il modo previsto per nidificare un Forse in un Forse?


9
Non fa sanse avere null. È un'idea completamente rotta.
Andrej Bauer,

7
Non fare un forse-forse-foo che ha una semantica diversa da forse-foo. Forse è una monade , e una delle leggi della monade è quella M M xe M xdovrebbe avere la stessa semantica.
Eric Lippert,

2
Potresti prendere in considerazione la progettazione delle versioni precedenti di Visual Basic, che avevano Nothing (riferimento a nessun oggetto), Null (semantica null del database), Empty (una variabile non inizializzata) e Missing (non è stato passato un parametro facoltativo). Questo design era complicato e per molti versi incoerente, ma c'è una ragione per cui quei concetti non si confondevano l'uno con l'altro.
Eric Lippert,

3
Maybe aa+1Maybe Maybe aa+1+1UserInput a

12
@EricLippert: è falso che " M (M x)e M xdovrebbe avere la stessa semantica". Prendiamo M = Listad esempio: gli elenchi di elenchi non sono la stessa cosa degli elenchi. Quando Mè una monade, c'è una trasformazione (cioè la moltiplicazione Monade) dal M (M x)al M xche spiega la relazione tra loro, ma non hanno la stessa semantica "".
Andrej Bauer,

Risposte:


14

Il nullvalore come regalo predefinito ovunque è solo un'idea davvero rotta , quindi dimenticalo.

Dovresti sempre avere esattamente il concetto che meglio descrive i tuoi dati reali. Se hai bisogno di un tipo che indica "sconosciuto", "niente" e "valore", dovresti avere esattamente quello. Ma se non si adatta alle tue reali esigenze, non dovresti farlo. È una buona idea vedere cosa usano gli altri e cosa hanno proposto, ma non è necessario seguirli alla cieca.

Le persone che progettano librerie standard cercano di indovinare modelli di utilizzo comuni e di solito lo fanno bene, ma se c'è una cosa specifica di cui hai bisogno, dovresti definirla tu stesso. Ad esempio, in Haskell, è possibile definire:

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

Potrebbe anche essere il caso di utilizzare qualcosa di più descrittivo che è più facile da ricordare:

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

È possibile definire 10 tipi di questo tipo da utilizzare per diversi scenari. Lo svantaggio è che non avrai a disposizione funzioni di libreria preesistenti (come quelle di Maybe), ma di solito si tratta di un dettaglio. Non è difficile aggiungere la tua funzionalità.


Ha perfettamente senso quando lo vedi spiegato così. Sono per lo più interessato alle idee, il supporto alle biblioteche esistenti è solo un bonus :)
Stijn

Se solo la barriera alla creazione di tali tipi fosse inferiore in molte lingue. : /
Raffaello

Se solo le persone smettessero di usare le lingue degli anni '60 (o le loro reincarnazioni dagli anni '80).
Andrej Bauer il

@AndrejBauer: che cosa? ma poi i teorici non avrebbero nulla di cui lamentarsi!
Yttrill,

Non ci lamentiamo, siamo in alto.
Andrej Bauer l'

4

Per quanto ne so, il valore nullin C # è un valore possibile per alcune variabili, a seconda del suo tipo (ho ragione?). Ad esempio, istanze di qualche classe. Per il resto dei tipi (come int, boolecc.) Puoi aggiungere questo valore di eccezione dichiarando variabili con int?o bool?invece (questo è esattamente ciò che fa il Maybecostruttore, come descriverò in seguito).

Il Maybecostruttore del tipo dalla programmazione funzionale aggiunge questo nuovo valore di eccezione per un dato tipo di dati. Quindi se Intè il tipo di numeri interi o Gameè il tipo di stato di un gioco, allora Maybe Intha tutti i numeri interi più un valore nullo (a volte chiamato nulla ). Lo stesso per Maybe Game. Qui non ci sono tipi associati al nullvalore. Lo aggiungi quando ne hai bisogno.

IMO, quest'ultimo approccio è il migliore per qualsiasi linguaggio di programmazione.


Sì, la tua comprensione di C # è corretta. Quindi posso dedurre dalla tua risposta che suggerisci di annidare Maybee di abbandonare del nulltutto?
Stijn,

2
La mia opinione è su come dovrebbe essere una lingua. Non conosco davvero le migliori pratiche di programmazione C #. Nel tuo caso, l'opzione migliore sarebbe definire un tipo di dati come descritto da @Andrej Bauer, ma penso che non puoi farlo in C #.
Enorme

@Euge: i tipi di somma algebrica possono essere (approssimativamente) approssimati con il sottotipo OO classico e l'invio di messaggi polimorfici di sottotipo. È così che viene fatto in Scala, ad esempio, con una svolta aggiuntiva per consentire i tipi chiusi . Il tipo diventa una superclasse astratta, i costruttori del tipo diventano sottoclassi concrete, la corrispondenza dei modelli sui costruttori può essere approssimata spostando i casi in metodi sovraccarichi nelle sottoclassi concrete o nei isinstancetest. Scala ha anche una corrispondenza "corretta" dei modelli, e C♯ in realtà anche recentemente ha ottenuto modelli semplici.
Jörg W Mittag,

La "svolta aggiuntiva" che ho citato per Scala è che oltre ai modificatori "standard" "virtuali" (per una classe da cui è possibile ereditare) e final(per una classe da cui non è possibile ereditare), ha anche sealed, per un classe che può essere estesa solo all'interno della stessa unità di compilazione . Ciò significa che il compilatore può conoscere staticamente tutte le possibili sottoclassi (purché tutte siano esse stesse sealedo final) e quindi eseguire controlli di esaustività per le corrispondenze dei pattern, il che non è possibile staticamente per un linguaggio con caricamento del codice di runtime ed eredità illimitata.
Jörg W Mittag,

Ovviamente puoi progettare i tipi con quella semantica in C #. Si noti che C # non consente il tipo forse incorporato (chiamato Nullable in C #) per essere nidificato. int??è illegale in C #.
Eric Lippert,

3

Se puoi definire unioni di tipo, come X or Y, e se hai un tipo chiamato Nullche rappresenta solo il nullvalore (e nient'altro), allora per qualsiasi tipo T, in T or Nullrealtà non è così diverso da Maybe T. L'unico problema nullè quando la lingua tratta un tipo Tin modo T or Nullimplicito, creando nullun valore valido per ogni tipo (tranne i tipi primitivi nelle lingue che li hanno). Questo è ciò che accade ad esempio in Java dove Stringaccetta anche una funzione che accetta un null.

Se trattate i tipi come set di valori e orcome unione di set, quindi (T or Null) or Nullrappresenta il set di valori T ∪ {null} ∪ {null}, che è lo stesso di T or Null. Esistono compilatori che eseguono questo tipo di analisi (SBCL). Come detto nei commenti, non è possibile distinguere facilmente tra Maybe Te Maybe Maybe Tquando si visualizzano i tipi come domini (d'altra parte, tale vista è abbastanza utile per i programmi tipizzati dinamicamente). Ma potresti anche mantenere i tipi come espressioni algebriche, dove (T or Null) or Nullrappresenta più livelli di Maybes.


2
In realtà, è importante che Maybe (Maybe X)non sia la stessa cosa di Maybe X. Nothingnon è la stessa cosa di Just Nothing. La confluenza Maybe (Maybe X)con Maybe Xrende impossibile il trattamento Maybe _polimorfico.
Gilles 'SO- smetti di essere malvagio' il

1

Devi scoprire cosa desideri esprimere e quindi trovare un modo per esprimerlo.

Innanzitutto, hai valori nel tuo dominio problematico (come numeri, stringhe, record dei clienti, valori booleani, ecc.). In secondo luogo, ci sono alcuni valori generici aggiuntivi oltre ai valori del dominio problematico: ad esempio "niente" (l'assenza nota di un valore), "sconosciuto" (nessuna conoscenza sulla presenza o l'assenza di un valore), non menzionato era " qualcosa "(c'è sicuramente un valore, ma non sappiamo quale). Forse puoi pensare ad altre situazioni.

Se si desidera essere in grado di rappresentare tutti i valori più questi tre valori aggiuntivi, si creerebbe un enum con casi "niente", "sconosciuto", "qualcosa" e "valore" e si passa da lì.

In alcune lingue alcuni casi sono più semplici. Ad esempio, in Swift hai per ogni tipo T il tipo "T opzionale" che ha come possibili valori pari a zero più tutti i valori di T. Questo ti consente di gestire facilmente molte situazioni ed è perfetto se non hai "niente" e " valore". Potresti usarlo se hai "sconosciuto" e "valore". Si potrebbe non utilizzare se è necessario gestire "nulla", "sconosciuto" e "valore". Altre lingue possono usare "null" o "forse", ma questa è solo un'altra parola.

In JSON, hai dizionari con coppie chiave / valore. In questo caso, una chiave potrebbe non essere presente in un dizionario o potrebbe avere un valore null. Quindi descrivendo attentamente come vengono archiviate le cose, puoi rappresentare valori generici più due valori aggiuntivi.

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.