È una cattiva idea restituire tipi di dati diversi da una singola funzione in un linguaggio tipizzato in modo dinamico?


65

La mia lingua principale è di tipo statico (Java). In Java, è necessario restituire un singolo tipo da ogni metodo. Ad esempio, non è possibile avere un metodo che restituisce un condizionatamente Stringo ritorna condizionalmente una Integer. Ma in JavaScript, ad esempio, questo è molto possibile.

In un linguaggio tipicamente statico capisco perché questa è una cattiva idea. Se ogni metodo restituito Object(il genitore comune da cui ereditano tutte le classi), tu e il compilatore non avete idea di cosa avete a che fare. Dovrai scoprire tutti i tuoi errori in fase di esecuzione.

Ma in un linguaggio tipizzato in modo dinamico, potrebbe non esserci nemmeno un compilatore. In un linguaggio tipizzato in modo dinamico, non è ovvio per me una funzione che restituisce più tipi è una cattiva idea. Il mio background in linguaggi statici mi evita di scrivere tali funzioni, ma temo di avere una mentalità molto attenta a una funzionalità che potrebbe rendere il mio codice più pulito in modi che non riesco a vedere.


Modifica : ho intenzione di rimuovere il mio esempio (fino a quando non riesco a pensare a uno migliore). Penso che stia guidando le persone a rispondere a un punto che non sto cercando di esprimere.


Perché non generare un'eccezione nel caso di errore?
TrueWill,

1
@True, lo affronterò nella frase successiva.
Daniel Kaplan,

Hai notato che questa è una pratica comune in linguaggi più dinamici? È comune nei linguaggi funzionali , ma lì le regole sono rese molto esplicite. E so che è comune, soprattutto in JavaScript, consentire agli argomenti di essere di tipi diversi (poiché questo è essenzialmente l'unico modo per fare il sovraccarico delle funzioni), ma raramente ho visto questo applicato per restituire valori. L'unico esempio che mi viene in mente è PowerShell con il suo wrapping / wrapping di array per lo più automatico ovunque, e i linguaggi di scripting sono un po 'un caso eccezionale.
Aaronaught,

3
Non menzionato, ma ci sono molti esempi (anche in Java Generics) di funzioni che prendono un tipo restituito come parametro; ad es. in Common Lisp abbiamo (coerce var 'string)rese a stringo (concatenate 'string this that the-other-thing)similmente. Ho scritto anche cose del genere ThingLoader.getThingById (Class<extends FindableThing> klass, long id). E lì, potrei solo restituire qualcosa che subclasse quello che hai chiesto: loader.getThingById (SubclassA.class, 14)potrebbe restituire un oggetto SubclassBche si estende SubclassA...
BRPocock,

1
Le lingue tipizzate dinamicamente sono come lo Spoon nel film The Matrix . Non tentare di definire il cucchiaio come una stringa o un numero . Sarebbe impossibile. Invece ... prova solo a capire la verità. Che non c'è Spoon .
Reactgular,

Risposte:


42

Al contrario di altre risposte, ci sono casi in cui è accettabile la restituzione di tipi diversi.

Esempio 1

sum(2, 3)  int
sum(2.1, 3.7)  float

In alcuni linguaggi tipicamente statici, ciò comporta sovraccarichi, quindi possiamo considerare che esistono diversi metodi, ognuno dei quali restituisce il tipo predefinito predefinito. Nei linguaggi dinamici, questa può essere la stessa funzione, implementata come:

var sum = function (a, b) {
    return a + b;
};

Stessa funzione, diversi tipi di valore restituito.

Esempio 2

Immagina di ottenere una risposta da un componente OpenID / OAuth. Alcuni provider OpenID / OAuth possono contenere più informazioni, come l'età della persona.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: 'hello.world@example.com',
//     age: 27
// }

Altri avrebbero il minimo, sarebbe un indirizzo e-mail o lo pseudonimo.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: 'hello.world@example.com'
// }

Ancora una volta, stessa funzione, risultati diversi.

Qui, il vantaggio di restituire tipi diversi è particolarmente importante in un contesto in cui non ti interessano i tipi e le interfacce, ma quali oggetti contengano effettivamente. Ad esempio, immaginiamo che un sito Web contenga un linguaggio maturo. Quindi findCurrent()può essere usato in questo modo:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

Rifattorizzare questo in codice in cui ogni provider avrà la propria funzione che restituirà un tipo fisso ben definito non solo degraderebbe la base di codice e causerebbe la duplicazione del codice, ma non apporterebbe alcun vantaggio. Si potrebbe finire per fare orrori come:

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}

5
"Nelle lingue tipizzate staticamente, ciò comporta sovraccarichi" Penso che intendessi "in alcune lingue tipizzate staticamente" :) Buone lingue tipizzate staticamente non richiedono un sovraccarico per qualcosa come il tuo sumesempio.
Andres F.

17
Per il vostro esempio specifico, in considerazione Haskell: sum :: Num a => [a] -> a. Puoi sommare un elenco di tutto ciò che è un numero. A differenza di JavaScript, se si tenta di sommare qualcosa che non è un numero, l'errore verrà rilevato al momento della compilazione.
Andres F.

3
@MainMa In Scala, Iterator[A]ha il metodo def sum[B >: A](implicit num: Numeric[B]): B, che consente nuovamente di sommare qualsiasi tipo di numero e viene controllato al momento della compilazione.
Petr Pudlák,

3
@Bakuriu Naturalmente puoi scrivere una tale funzione, anche se prima dovresti sovraccaricare le +stringhe (implementando Num, che è una terribile idea dal punto di vista progettuale ma legale) o inventare un altro operatore / funzione che è sovraccaricato da numeri interi e stringhe rispettivamente. Haskell ha un polimorfismo ad hoc tramite classi di tipi. Un elenco che contiene sia numeri interi sia stringhe è molto più difficile (forse impossibile senza estensioni del linguaggio) ma una questione piuttosto diversa.

2
Non sono particolarmente colpito dal tuo secondo esempio. Questi due oggetti sono dello stesso "tipo" concettuale, è solo che in alcuni casi alcuni campi non sono definiti o popolati. Potresti rappresentarlo bene anche in un linguaggio tipicamente statico con nullvalori.
Aaronaught,

31

In generale, è una cattiva idea per le stesse ragioni per cui l'equivalente morale in un linguaggio tipicamente statico è una cattiva idea: non hai idea di quale tipo concreto venga restituito, quindi non hai idea di cosa puoi fare con il risultato (a parte il poche cose che possono essere fatte con qualsiasi valore). In un sistema di tipi statici, hai annotazioni verificate dal compilatore dei tipi restituiti e simili, ma in un linguaggio dinamico esiste ancora la stessa conoscenza: è solo informale e memorizzata nel cervello e nella documentazione piuttosto che nel codice sorgente.

In molti casi, tuttavia, esiste una rima e una ragione per cui viene restituito il tipo e l'effetto è simile al sovraccarico o al polimorfismo parametrico nei sistemi di tipo statico. In altre parole, il tipo di risultato è prevedibile, non è altrettanto semplice da esprimere.

Si noti tuttavia che potrebbero esserci altri motivi per cui una funzione specifica è progettata in modo sumerrato : ad esempio, una funzione che restituisce false su input non validi è una cattiva idea principalmente perché quel valore di ritorno è inutile e soggetto a errori (0 <-> false confusion).


40
I miei due centesimi: lo odio quando succede. Ho visto librerie JS che restituiscono null quando non ci sono risultati, un oggetto su un risultato e una matrice su due o più risultati. Quindi, invece di eseguire semplicemente il looping di un array, sei costretto a determinare se è null e se non lo è, se è un array e, se lo è, fai una cosa, altrimenti fai qualcos'altro. Soprattutto dal momento che, in senso normale, qualsiasi sviluppatore sano aggiungerà semplicemente l'oggetto a un array e riciclerà la logica del programma dalla gestione di un array.
phyrfox il

10
@Izkata: non credo NaNsia affatto un "contrappunto". NaN è in realtà un input valido per qualsiasi calcolo in virgola mobile. Puoi fare qualsiasi cosa con NaNquella che potresti fare con un numero reale. È vero, il risultato finale di detto calcolo potrebbe non essere molto utile per te, ma non porterà a strani errori di runtime e non è necessario verificarlo continuamente - puoi semplicemente controllare una volta alla fine di un serie di calcoli. NaN non è un tipo diverso , è solo un valore speciale , un po 'come un Null Object .
Aaronaught,

5
@Aaronaught D'altra parte, NaNcome l'oggetto null, tende a nascondersi quando si verificano errori, solo per farli apparire in seguito. Ad esempio, è probabile che il tuo programma esploda se NaNscivola in un ciclo condizionale.
Izkata,

2
@Izkata: NaN e null hanno comportamenti totalmente diversi. Un riferimento null esploderà immediatamente al momento dell'accesso, un NaN si propaga. Perché questo accada è ovvio, ti permette di scrivere espressioni matematiche senza dover controllare il risultato di ogni sottoespressione. Personalmente, vedo nulla di più dannoso, perché NaN rappresenta un concetto numerico adeguato senza il quale non si può scappare e, matematicamente, propagare è il comportamento corretto.
Phoshi,

4
Non so perché questo si sia trasformato in un argomento "nullo contro tutto il resto". Stavo semplicemente sottolineando che il NaNvalore non è (a) in realtà un diverso tipo di ritorno e (b) ha una semantica in virgola mobile ben definita, e quindi non si qualifica davvero come un analogo di un valore di ritorno tipizzato in modo variabile. Non è poi così diverso da zero nell'aritmetica dei numeri interi, in realtà; se uno zero "accidentalmente" scivola nei tuoi calcoli, allora è probabile che finisca con zero come risultato o un errore di divisione per zero. I valori di "infinito" definiti da IEEE in virgola mobile sono anch'essi malvagi?
Aaronaught,

26

Nei linguaggi dinamici, non dovresti chiedere se restituiscono tipi diversi ma oggetti con API diverse . Dato che la maggior parte dei linguaggi dinamici non si preoccupa davvero dei tipi, ma usa varie versioni della tipizzazione duck .

Quando si restituiscono tipi diversi ha senso

Ad esempio questo metodo ha senso:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

Perché sia ​​il file che un elenco di stringhe sono (in Python) iterabili che restituiscono stringa. Tipi molto diversi, la stessa API (a meno che qualcuno non provi a chiamare metodi di file in un elenco, ma questa è una storia diversa).

È possibile restituire in modo condizionale listo tuple( tupleè un elenco immutabile in Python).

Formalmente anche facendo:

def do_something():
    if ...: 
        return None
    return something_else

o:

function do_something(){
   if (...) return null; 
   return sth;
}

restituisce tipi diversi, poiché sia ​​python Noneche Javascript nullsono tipi a sé stanti.

Tutti questi casi d'uso avrebbero la loro controparte in linguaggi statici, la funzione restituirebbe semplicemente un'interfaccia corretta.

Quando si restituiscono oggetti con diverse API in modo condizionale è una buona idea

Se la restituzione di API diverse sia una buona idea, nella maggior parte dei casi l'IMO non ha senso. L'unico esempio sensato che viene in mente è qualcosa di simile a quello che ha detto @MainMa : quando la tua API può fornire una quantità variabile di dettagli, potrebbe avere senso restituire ulteriori dettagli quando disponibili.


4
Buona risposta. Vale la pena notare che l'equivalente tipizzato staticamente / Java deve avere una funzione che restituisca un'interfaccia piuttosto che uno specifico tipo concreto, con l'interfaccia che rappresenta l'API / astrazione.
Mikera,

1
Penso che tu stia pignolando, in Python un elenco e una tupla possono essere di "tipi" diversi, ma sono dello stesso "tipo anatra", cioè supportano lo stesso insieme di operazioni. E casi come questo sono esattamente i motivi per cui è stata introdotta la tipizzazione anatra. Puoi eseguire long x = getInt () anche in Java.
Kaerber,

"Restituzione di tipi diversi" significa "restituzione di oggetti con API diverse". (Alcuni linguaggi rendono il modo in cui l'oggetto è costruito una parte fondamentale dell'API, altri no; è una questione di quanto sia espressivo il sistema di tipi.) Nell'esempio di elenco / file, do_filerestituisce una iterabile di stringhe in entrambi i modi.
Gilles 'SO- smetti di essere cattivo' il

1
In quell'esempio di Python, puoi anche chiamare iter()sia l'elenco che il file per assicurarti che il risultato possa essere usato come iteratore solo in entrambi i casi.
Remco Gerlich,

1
il ritorno None or somethingè un killer per l'ottimizzazione delle prestazioni fatto da strumenti come PyPy, Numba, Pyston e altri. Questo è uno dei python-ism che rendono python non così veloce.
Matt,

9

La tua domanda mi fa venir voglia di piangere un po '. Non per l'uso esemplificativo che hai fornito, ma perché qualcuno involontariamente porterà troppo lontano questo approccio. È a un passo dal codice ridicolmente non mantenibile.

La condizione di errore usa il tipo di caso di senso, e il modello null (tutto deve essere un modello) in linguaggi tipicamente statici fa lo stesso tipo di cose. La chiamata di funzione restituisce objecto oppure ritorna null.

Ma è un breve passo a dire "ho intenzione di usare questo per creare un modello di fabbrica " e tornare sia fooo baroppure baza seconda dell'umore della funzione. Il debug diventerà un incubo quando il chiamante si aspetta fooma è stato dato bar.

Quindi non penso che tu sia di mentalità chiusa. Sei stato adeguatamente cauto su come usare le funzionalità del linguaggio.

Divulgazione: il mio background è in linguaggi tipicamente statici e in genere ho lavorato su team più grandi e diversificati in cui la necessità di codice gestibile era piuttosto elevata. Quindi anche la mia prospettiva è probabilmente distorta.


6

L'uso di Generics in Java consente di restituire un tipo diverso, pur mantenendo la sicurezza del tipo statico. È sufficiente specificare il tipo che si desidera restituire nel parametro di tipo generico della chiamata di funzione.

Se sia possibile utilizzare un approccio simile in Javascript è, ovviamente, una domanda aperta. Poiché Javascript è un linguaggio tipizzato in modo dinamico, restituire una objectsembra la scelta più ovvia.

Se vuoi sapere dove potrebbe funzionare uno scenario di ritorno dinamico quando sei abituato a lavorare in un linguaggio tipicamente statico, considera la dynamicparola chiave in C #. Rob Conery è stato in grado di scrivere con successo un mappatore relazionale di oggetti in 400 righe di codice usando la dynamicparola chiave.

Ovviamente, tutto ciò dynamicche fa è avvolgere una objectvariabile con una certa sicurezza di tipo runtime.


4

È una cattiva idea, penso, di restituire condizionalmente diversi tipi. Uno dei modi in cui questo si presenta frequentemente per me è se la funzione può restituire uno o più valori. Se è necessario restituire un solo valore, può sembrare ragionevole restituire il valore, anziché impacchettarlo in un array, per evitare di doverlo decomprimere nella funzione chiamante. Tuttavia, questo (e la maggior parte degli altri casi) impone al chiamante di distinguere e gestire entrambi i tipi. La funzione sarà più facile da ragionare se restituisce sempre lo stesso tipo.


4

La "cattiva pratica" esiste indipendentemente dal fatto che la tua lingua sia tipizzata staticamente. Il linguaggio statico fa di più per allontanarti da queste pratiche e potresti trovare più utenti che si lamentano di "cattive pratiche" in un linguaggio statico perché è un linguaggio più formale. Tuttavia, i problemi sottostanti sono presenti in un linguaggio dinamico e puoi determinare se sono giustificati.

Ecco la parte discutibile di ciò che proponi. Se non so quale tipo viene restituito, non posso utilizzare immediatamente il valore restituito. Devo "scoprire" qualcosa al riguardo.

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

Spesso questo tipo di commutazione nel codice è semplicemente una cattiva pratica. Rende il codice più difficile da leggere. In questo esempio, vedi perché è popolare lanciare e catturare casi eccezionali. In altre parole: se la tua funzione non può fare ciò che dice di fare, non dovrebbe tornare correttamente . Se chiamo la tua funzione, voglio fare questo:

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Poiché la prima riga restituisce correttamente, suppongo che totalcontenga effettivamente la somma dell'array. Se non ritorna con successo, passiamo a un'altra pagina del mio programma.

Usiamo un altro esempio che non riguarda solo la propagazione degli errori. Forse sum_of_array cerca di essere intelligente e restituire una stringa leggibile dall'uomo in alcuni casi, come "Questa è la mia combinazione di armadietti!" se e solo se l'array è [11,7,19]. Ho problemi a pensare a un buon esempio. Ad ogni modo, si applica lo stesso problema. Devi ispezionare il valore restituito prima di poter fare qualsiasi cosa con esso:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

Si potrebbe sostenere che sarebbe utile che la funzione restituisca un numero intero o un float, ad esempio:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

Ma quei risultati non sono di tipo diverso, per quanto ti riguarda. Li tratterai entrambi come numeri, ed è tutto ciò che ti interessa. Quindi sum_of_array restituisce il tipo di numero. Questo è il polimorfismo.

Quindi ci sono alcune pratiche che potresti violare se la tua funzione può restituire più tipi. Conoscerli ti aiuterà a determinare se la tua particolare funzione deve comunque restituire più tipi.


Scusa, ti ho portato fuori strada con il mio povero esempio. Dimentica di averlo menzionato in primo luogo. Il tuo primo paragrafo è sul punto. Mi piace anche il tuo secondo esempio.
Daniel Kaplan,

4

In realtà, non è affatto raro restituire tipi diversi anche in una lingua tipizzata staticamente. Ecco perché abbiamo tipi di unione, per esempio.

In effetti, i metodi in Java restituiscono quasi sempre uno dei quattro tipi: un tipo di oggetto o nullun'eccezione o non ritornano mai affatto.

In molte lingue, le condizioni di errore sono modellate come subroutine che restituiscono un tipo di risultato o un tipo di errore. Ad esempio, in Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

Questo è un esempio stupido, ovviamente. Il tipo restituito significa "restituisce una stringa o un decimale". Per convenzione, il tipo sinistro è il tipo di errore (in questo caso, una stringa con il messaggio di errore) e il tipo giusto è il tipo di risultato.

Ciò è simile alle eccezioni, ad eccezione del fatto che le eccezioni sono anche costrutti di flusso di controllo. Sono, infatti, equivalenti in potenza espressiva a GOTO.


il metodo in Java che restituisce l'eccezione suona strano. " Return un un'eccezione " ... hmm
moscerino

3
Un'eccezione è un possibile risultato di un metodo in Java. In realtà, ho dimenticato un quarto tipo: Unit(un metodo non è necessario per tornare affatto). È vero, in realtà non si utilizza la returnparola chiave, ma è comunque un risultato che ritorna dal metodo. Con le eccezioni verificate, viene persino menzionato esplicitamente nella firma del tipo.
Jörg W Mittag,

Vedo. Il tuo punto sembra avere dei meriti, ma il modo in cui viene presentato non lo fa sembrare avvincente ... piuttosto opposto
moscerino

4
In molte lingue, le condizioni di errore sono modellate come una subroutine che restituisce un tipo di risultato o un tipo di errore. Le eccezioni sono un'idea simile, tranne per il fatto che sono anche costrutti di flusso di controllo (uguali in potenza espressiva a GOTO, in realtà).
Jörg W Mittag,

4

Nessuna risposta ha ancora menzionato i principi SOLID. In particolare, dovresti seguire il principio di sostituzione di Liskov, secondo cui qualsiasi classe che riceve un tipo diverso dal tipo previsto può comunque funzionare con qualunque cosa ottenga, senza fare nulla per testare quale tipo viene restituito.

Quindi, se si gettano alcune proprietà extra su un oggetto o si avvolge una funzione restituita con un qualche tipo di decoratore che realizza ancora ciò che la funzione originale doveva realizzare, sei buono, a condizione che nessun codice che chiama la tua funzione si basi mai su questo comportamento, in qualsiasi percorso di codice.

Invece di restituire una stringa o un numero intero, un esempio migliore potrebbe essere che restituisce un sistema di irrigazione o un gatto. Questo va bene se tutto il codice chiamante sta per fare è chiamare functionInQuestion.hiss (). In effetti hai un'interfaccia implicita che il codice chiamante si aspetta e un linguaggio tipizzato in modo dinamico non ti costringerà a rendere esplicita l'interfaccia.

Sfortunatamente, i tuoi colleghi probabilmente lo faranno, quindi probabilmente dovrai fare lo stesso lavoro nella tua documentazione, tranne per il fatto che non esiste un modo universalmente accettato, conciso e analizzabile dalla macchina per farlo - come accade quando si definisce un'interfaccia in una lingua che li ha.


3

L'unico posto in cui mi vedo inviare tipi diversi è per input non validi o "eccezioni per poveri" in cui la condizione "eccezionale" non è molto eccezionale. Ad esempio, dal mio repository di funzioni di utilità PHP questo esempio abbreviato:

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

La funzione restituisce nominalmente un BOOLEANO, tuttavia restituisce NULL su input non valido. Si noti che da PHP 5.3, tutte le funzioni PHP interne si comportano in questo modo . Inoltre, alcune funzioni PHP interne restituiscono FALSE o INT sull'ingresso nominale, vedere:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)

4
Ed è per questo che odio PHP. Bene, uno dei motivi, comunque.
Aaronaught,

1
Un downvote perché a qualcuno non piace il linguaggio in cui sviluppo in modo professionale?!? Aspetta di scoprire che sono ebreo! :)
dotancohen,

3
Non dare sempre per scontato che le persone che commentano e le persone che hanno votato per difetto siano le stesse persone.
Aaronaught,

1
Preferisco avere un'eccezione invece di null, perché (a) fallisce a gran voce , quindi è più probabile che venga risolto in fase di sviluppo / test, (b) è facile da gestire, perché posso catturare tutte le eccezioni rare / impreviste una volta per tutta la mia applicazione (nel mio front controller) e basta registrarla (di solito invio e-mail al team di sviluppo), in modo che possa essere riparata in seguito. E in realtà odio il fatto che la libreria PHP standard utilizzi principalmente l'approccio "return null" - in realtà rende il codice PHP più soggetto a errori, a meno che non si controlli tutto isset(), il che è troppo oneroso.
scriptin,

1
Inoltre, se ritorni nullsu un errore, per favore per favore chiariscilo in un blocco documenti (ad es. @return boolean|null), Quindi se un giorno dovessi imbatterti nel tuo codice non dovrei controllare il corpo della funzione / metodo.
scriptin,

3

Non penso sia una cattiva idea! Contrariamente a questa opinione più comune, e come già sottolineato da Robert Harvey, linguaggi tipicamente statici come Java hanno introdotto Generics esattamente per situazioni come quella che si sta chiedendo. In realtà Java cerca di mantenere (ove possibile) la sicurezza dei caratteri al momento della compilazione, e talvolta i generici evitano la duplicazione del codice, perché? Perché è possibile scrivere lo stesso metodo o la stessa classe che gestisce / restituisce tipi diversi . Farò solo un brevissimo esempio per mostrare questa idea:

Java 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

In un linguaggio tipizzato dinamico, poiché al momento della compilazione non hai alcuna sicurezza di battitura, sei assolutamente libero di scrivere lo stesso codice che funziona per molti tipi. Poiché anche in un linguaggio tipicamente statico sono stati introdotti i generici per risolvere questo problema, è chiaramente un indizio che scrivere una funzione che restituisce tipi diversi in un linguaggio dinamico non sia una cattiva idea.


Un altro downvote senza spiegazione! Grazie SE community!
thermz,

2
Abbi una simpatia +1. Dovresti provare ad aprire la tua risposta con una risposta più chiara e diretta e astenersi dal commentare risposte diverse. (Ti darei un altro +1, dato che sono d'accordo con te, ma ne ho solo uno da dare.)
DougM,

3
I generici non esistono in linguaggi tipicamente dinamici (che io conosco). Non ci sarebbe motivo per loro. La maggior parte dei generici è un caso speciale, in cui il "contenitore" è più interessante di quello che contiene (motivo per cui alcune lingue, come Java, usano effettivamente la cancellazione del tipo). Il tipo generico è in realtà un tipo a sé stante. I metodi generici , senza un tipo generico effettivo, come nel tuo esempio qui, sono quasi sempre solo zucchero di sintassi per una semantica assegnata e lanciata. Non è un esempio particolarmente convincente di ciò che la domanda sta davvero ponendo.
Aaronaught,

1
Cerco di essere un po 'più sintetico: "Dato che anche in un linguaggio tipicamente statico sono stati introdotti i generici per risolvere questo problema, è chiaramente un indizio che scrivere una funzione che restituisce tipi diversi in un linguaggio dinamico non sia una cattiva idea". Meglio ora? Controlla la risposta di Robert Harvey o il commento di BRPocock e ti renderai conto che l'esempio di Generics è legato a questa domanda
thermz,

1
Non stare male, @thermz. I downvotes spesso dicono di più sul lettore che sullo scrittore.
Karl Bielefeldt,

2

Lo sviluppo di software è fondamentalmente un'arte e un mestiere di gestione della complessità. Cerchi di restringere il sistema nei punti che ti puoi permettere e limitare le opzioni in altri punti. L'interfaccia di Function è un contratto che aiuta a gestire la complessità del codice limitando una conoscenza necessaria per lavorare con qualsiasi pezzo di codice. Restituendo diversi tipi si espande significativamente l'interfaccia della funzione aggiungendo ad essa tutte le interfacce di diversi tipi restituiti E aggiungendo regole non visibili su quale interfaccia viene restituita.


1

Perl lo usa molto perché ciò che una funzione fa dipende dal suo contesto . Ad esempio, una funzione potrebbe restituire una matrice se utilizzata nel contesto dell'elenco o la lunghezza della matrice se utilizzata in un luogo in cui è previsto un valore scalare. Dal tutorial che è il primo successo per "contesto perl" , se lo fai:

my @now = localtime();

Quindi @now è una variabile di matrice (ecco cosa significa @) e conterrà una matrice come (40, 51, 20, 9, 0, 109, 5, 8, 0).

Se invece chiamate la funzione in un modo in cui il suo risultato dovrà essere uno scalare, con ($ le variabili sono scalari):

my $now = localtime();

poi fa qualcosa di completamente diverso: $ ora sarà qualcosa del tipo "Ven 9 gennaio 20:51:40 2009".

Un altro esempio che mi viene in mente è l'implementazione delle API REST, in cui il formato di ciò che viene restituito dipende da ciò che il cliente desidera. Ad esempio, HTML o JSON o XML. Sebbene tecnicamente quelli siano tutti flussi di byte, l'idea è simile.


Potresti voler menzionare il modo specifico in cui lo fa in perl - wantarray ... e forse un link a qualche discussione se è buona o cattiva - Uso di wantarray considerato dannoso in PerlMonks

Per coincidenza, ho a che fare con questo con un'API Java Rest che sto costruendo. Ho reso possibile per una risorsa restituire XML o JSON. Il tipo di denominatore comune più basso in quel caso è a String. Ora le persone chiedono documentazione sul tipo di reso. Gli strumenti java possono generarlo automaticamente se restituisco una classe, ma poiché ho scelto Stringnon posso generare automaticamente la documentazione. Col senno di poi / morale della storia, vorrei aver restituito un tipo per metodo.
Daniel Kaplan,

In senso stretto, ciò equivale a una forma di sovraccarico. In altre parole, ci sono più funzioni federate e, a seconda dei dettagli della chiamata, viene scelta una o l'altra versione.
Ian,

1

Nella terra dinamica è tutta una questione di dattilografia. La cosa più responsabile esposta / rivolta al pubblico è quella di avvolgere tipi potenzialmente diversi in un wrapper che dia loro la stessa interfaccia.

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

Occasionalmente potrebbe avere senso estrarre del tutto diversi tipi senza un wrapper generico ma onestamente negli anni 7ish di scrittura di JS, non è qualcosa che ho trovato ragionevole o conveniente fare molto spesso. Per lo più è qualcosa che farei nel contesto di ambienti chiusi come l'interno di un oggetto in cui le cose vengono messe insieme. Ma non è qualcosa che ho fatto abbastanza spesso che mi venga in mente qualche esempio.

Principalmente, consiglierei di smettere di pensare ai tipi del tutto. Ti occupi di tipi quando è necessario in un linguaggio dinamico. Non piu. È il punto. Non controllare il tipo di ogni singolo argomento. È qualcosa che sarei tentato di fare solo in un ambiente in cui lo stesso metodo potrebbe dare risultati incoerenti in modi non visibili (quindi sicuramente non fare qualcosa del genere). Ma non è il tipo che conta, è come funziona qualunque cosa tu mi dia.

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.