Come usare Pattern tipo di opzione in una lingua che non supporta i farmaci generici?

2

Sono stato interessato a iniziare a utilizzare l'approccio Null Object / per sostituire le mie vecchie abitudini di null-checking di più parti del mio codice. Ma, dai molti esempi che ho visto là fuori, sembra che sia un pattern usato in congiunzione con i generici.

Ottimo per C #, Java, Haxe e un sacco di altri ne sono sicuro.

NON così eccezionale per ActionScript 3.0 (che è quello che intendevo usarlo per).

Quindi, ho l'impressione sbagliata che ciò non sarà possibile / utile in un linguaggio che non supporta Generics? O c'è un modo per rimuoverlo senza di loro?

NOTA: sembra che lo schema " Oggetto nullo " mostrato in questo tutorial di PatternCraft non assomiglia molto al modo in cui Tipo di opzione è stato descritto in alcune delle altre discussioni in questo sito di stackexchange. Il suo modo sembra più simile a una factory class che sputa un derivato Null ??? di alcune classi base. Speravo che ci potesse essere un modo per creare oggetti Null senza dover scrivere una classe dedicata per ogni singolo caso in cui normalmente veniva usato un controllo null.

EDIT: Le mie scuse, non mi sono reso conto che "Null Object" e "Option Type" erano due bestie completamente differenti. Per mantenere comunque le risposte e il feedback pertinenti alla domanda, ho cambiato solo il titolo (originariamente era How to use Null Object / Option Type Pattern in a language that doesn't support generics? ).

    
posta bigp 08.08.2014 - 03:49
fonte

3 risposte

3

C'è una grande differenza tra il modello oggetto nullo ei tipi di opzioni.

L'oggetto nullo è puro schema OOP (quindi non generico), in cui un oggetto implementa una classe / interfaccia astratta in modo tale che chiamare il metodo attraverso l'interfaccia è lo stesso di non chiamare affatto il metodo. Ciò significa che il chiamante non ha idea che il tipo di istanza sia un oggetto Nullo. Quindi questi due casi sono equivalenti:

// without null object
IInterface obj = null;
if(obj != null)
    obj.method()

// with null object
IInterface obj = new NullObject();
obj.method()

I tipi di opzioni, d'altro canto, sono sicuri per il tipo per garantire che chi chiama il metodo sappia che esiste la possibilità che la variabile contenga "nessun valore valido". Questo dà al compilatore la potenza sia di avvertimento che di errore quando il programmatore prova a chiamare il metodo sulla variabile, che potrebbe contenere null .

[PSEUDOCODE (that looks like C#)]
// case A
Option<IInterface> obj = getObject();
obj.Method() // ERROR, possible "Null reference exception"

// case B
Option<IInterface> obj = getObject();
if (obj.HasValue)
    obj.Method() // OK, compiler knows that obj has valid value thanks to the if

Ma credo che i tipi di opzioni non abbiano molto senso nel linguaggio, che non li hanno come funzionalità linguistica (ad esempio la maggior parte dei linguaggi OOP), perché il compilatore non è implementato per rilevare cose come il primo caso (a meno si utilizza una sorta di controllo di correttezza del codice statico). In questo modo non ottieni nulla rispetto al normale controllo null . Il modello di oggetto nullo d'altra parte è naturale in tutte le lingue OOP. Anche quelli senza generici.

Un buon esempio di linguaggio OOP che supporta il tipo di opzione è C # ' Nullable . Sapete che i tipi di valore hanno sempre un valore valido e Nullable consente di specificare i casi in cui la variabile potrebbe non contenere un valore valido. Ma questo è solo per i tipi di valore.

    
risposta data 08.08.2014 - 08:32
fonte
3

Option è isomorfo a una raccolta che può essere sempre vuota o contenere un singolo elemento. Quindi, lo implementerai nello stesso modo in cui faresti qualsiasi altra raccolta.

Se puoi implementare un elenco, impostare, mappare, accatastare, accumulare, allineare, trie, trarre ecc. nella tua lingua, puoi implementare Option esattamente nello stesso modo.

Vedi AS3Commons Collections per un framework di collezioni completo implementato in ActionScript. (Nota: non sembra potente quanto quello di Scala, ma può almeno paragonarsi favorevolmente con quello di Java.) In sostanza, senza il polimorfismo parametrico, si perde la sicurezza del tipo, proprio come con Java 1.1, .NET. 1.0, C, Pascal, Modula, ecc.

    
risposta data 08.08.2014 - 17:40
fonte
3

Lo scopo dei tipi di opzioni è di consentire al compilatore di sapere esattamente dove un valore può essere vuoto in una lingua dove null semplicemente non esiste (tipico per qualsiasi linguaggio che supporta i tipi di dati algebrici). In questo modo, il compilatore può dare errori nel compilatore se si dimentica di verificare la presenza di vuoto. Ecco un esempio di Rust, un linguaggio imperativo con una sintassi simile a C:

let my_number : Option<int> = read_number();
return 2 * my_number;          // compile error

Questo non verrà compilato perché my_number non contiene un int ma un Option<int> (cioè un int che può essere vuoto). Il compilatore non consente la moltiplicazione su Option<int> .

Pertanto, è necessario abbinare lo schema my_number per estrarne il valore:

let my_number : Option<int> = read_number();
match (my_number) {
    Some(my_actual_number) =>
        return Some(2 * my_actual_number);  // ok
    None ->
        return None;                        // fallback if it's empty

Il modello che combina forza il programmatore per gestire ogni possibile scenario: in questo caso, il caso None . In questo modo impedisce il tipico problema di "dimenticato di controllare per null".

Tuttavia, in una lingua in cui null è pervasivo, i tipi di opzioni non sono altrettanto utili poiché tutto può già essere nullo in ogni caso. Quindi il compilatore non può davvero aiutarti (dato che inferire il nullability richiederebbe la risoluzione del problema di interruzione).

Il modello di oggetto nullo non è la stessa cosa, ma è un concetto correlato.

Nelle lingue che dispongono del supporto di prima classe per i tipi di opzioni, è consentito applicare un'operazione all'oggetto interno all'interno del tipo di opzione, se esistente. Serve come scorciatoia per l'attività comune di "fai X all'oggetto, ma se è vuoto, dimenticalo". Questo processo viene in genere chiamato "mappatura" e questa operazione può essere definita per tutti i tipi di opzioni .

L'operazione mappa accetta un oggetto opzione ( Option<T> ), oltre a una funzione che può agire sull'oggetto interno ( T -> R ) e produce il risultato della funzione racchiusa in un oggetto opzione ( Option<R> ).

In effetti, l'esempio sopra può essere riscritto in modo molto più sintetico utilizzando una mappa combinata con una funzione anonima:

let my_number : Option<int> = read_number();
return my_number.map(
    // use anonymous function to specify what to do
    |my_actual_number| 2 * my_actual_number
);

La mappa può essere pensata come una ricetta che:

  • Se l'oggetto opzione non è vuoto, applica la funzione fornita e restituisci il risultato in Some(...) .
  • Se l'oggetto opzione è di fatto vuoto, non fare nulla e restituire solo None .

Nel modello di oggetto nullo, si utilizza una classe "null" che possiede gli stessi metodi della classe non nulla (potrebbero essere fratelli nella gerarchia di ereditarietà), ma disposti in modo tale che le operazioni sul "null" "La classe semplicemente non fa niente. Quindi, il modello di oggetto nullo è simile in quanto realizza lo stesso obiettivo, anche se in un modo diverso, più orientato agli oggetti / alla dattilografia.

    
risposta data 08.08.2014 - 20:15
fonte

Leggi altre domande sui tag