I parametri nominati rendono il codice più facile da leggere, più difficile da scrivere
Quando sto leggendo un pezzo di codice, i parametri con nome possono introdurre un contesto che rende il codice più facile da capire. Consideriamo per esempio questo costruttore: Color(1, 102, 205, 170)
. Cosa diavolo significa? Anzi, Color(alpha: 1, red: 102, green: 205, blue: 170)
sarebbe molto più facile da leggere. Ma ahimè, il compilatore dice "no" - vuole Color(a: 1, r: 102, g: 205, b: 170)
. Quando si scrive codice utilizzando parametri denominati, si trascorre una quantità di tempo non necessaria a cercare i nomi esatti: è più facile dimenticare i nomi esatti di alcuni parametri piuttosto che dimenticare il loro ordine.
Questo una volta mi ha morso quando ho usato DateTime
un'API che aveva due classi di fratelli per punti e durate con interfacce quasi identiche. Mentre DateTime->new(...)
accettato un second => 30
argomento, il DateTime::Duration->new(...)
ricercato seconds => 30
e simili per altre unità. Sì, ha assolutamente senso, ma questo mi ha mostrato che i parametri nominati ≠ facilità d'uso.
I nomi cattivi non rendono nemmeno più facile la lettura
Un altro esempio di come i parametri nominati possano essere errati è probabilmente il linguaggio R. Questo pezzo di codice crea un diagramma di dati:
plot(plotdata$n, plotdata$mu, type="p", pch=17, lty=1, bty="n", ann=FALSE, axes=FALSE)
Si vede due argomenti posizionali per la x ed y righe di dati, e poi un elenco di parametri denominati. Ci sono molte altre opzioni con i valori predefiniti e solo quelli sono elencati i cui valori predefiniti volevo modificare o specificare esplicitamente. Una volta ignorato che questo codice utilizza numeri magici e potrebbe trarre vantaggio dall'uso di enum (se R ne avesse uno!), Il problema è che molti di questi nomi di parametri sono piuttosto indecifrabili.
pch
è in realtà il personaggio della trama, il glifo che verrà disegnato per ogni punto dati. 17
è un cerchio vuoto o qualcosa del genere.
lty
è il tipo di linea. Ecco 1
una linea continua.
bty
è il tipo di scatola. Impostandolo per "n"
evitare che un riquadro venga disegnato attorno alla trama.
ann
controlla l'aspetto delle annotazioni degli assi.
Per qualcuno che non sa cosa significhi ogni abbreviazione, queste opzioni sono piuttosto confuse. Questo rivela anche perché R utilizza queste etichette: non come codice autocompattante, ma (essendo un linguaggio tipizzato dinamicamente) come chiavi per mappare i valori alle loro variabili corrette.
Proprietà di parametri e firme
Le firme di funzione possono avere le seguenti proprietà:
- Gli argomenti possono essere ordinati o non ordinati,
- chiamato o senza nome,
- richiesto o facoltativo.
- Le firme possono anche essere sovraccaricate per dimensione o tipo,
- e può avere dimensioni non specificate con varargs.
Lingue diverse arrivano a coordinate diverse di questo sistema. In C, gli argomenti sono ordinati, senza nome, sempre richiesti e possono essere varargs. In Java la situazione è simile, tranne per il fatto che le firme possono essere sovraccaricate. Nell'obiettivo C, le firme sono ordinate, denominate, richieste e non possono essere sovraccaricate perché sono solo zucchero sintattico attorno a C.
I linguaggi tipizzati dinamicamente con varargs (interfacce da riga di comando, Perl, ...) possono emulare parametri nominali opzionali. Le lingue con sovraccarico della dimensione della firma hanno qualcosa di simile ai parametri opzionali posizionali.
Come non implementare i parametri denominati
Quando pensiamo ai parametri nominati, di solito assumiamo parametri nominati, opzionali, non ordinati. L'attuazione di questi è difficile.
I parametri opzionali potrebbero avere valori predefiniti. Questi devono essere specificati dalla funzione chiamata e non devono essere compilati nel codice chiamante. Altrimenti, i valori predefiniti non possono essere aggiornati senza ricompilare tutto il codice dipendente.
Ora una domanda importante è come gli argomenti vengono effettivamente passati alla funzione. Con i parametri ordinati, gli arg possono essere passati in un registro o nel loro ordine inerente allo stack. Quando escludiamo i registri per un momento, il problema è come mettere nello stack argomenti opzionali non ordinati.
Per questo, abbiamo bisogno di un po 'di ordine sugli argomenti opzionali. Cosa succede se il codice di dichiarazione viene modificato? Poiché l'ordine è irrilevante, un riordino nella dichiarazione di funzione non dovrebbe modificare la posizione dei valori nello stack. Dovremmo anche considerare se è possibile aggiungere un nuovo parametro opzionale. Dal punto di vista degli utenti questo sembra essere così, perché il codice che non ha usato quel parametro in precedenza dovrebbe funzionare con il nuovo parametro. Quindi questo esclude ordini come l'uso dell'ordine nella dichiarazione o l'uso dell'ordine alfabetico.
Considera questo anche alla luce del sottotipo e del Principio di sostituzione di Liskov - nell'output compilato, le stesse istruzioni dovrebbero essere in grado di invocare il metodo su un sottotipo con possibilmente nuovi parametri denominati e su un supertipo.
Possibili implementazioni
Se non possiamo avere un ordine definitivo, quindi abbiamo bisogno di una struttura di dati non ordinata.
L'implementazione più semplice è semplicemente passare il nome dei parametri insieme ai valori. Ecco come vengono emulati i parametri con nome in Perl o con gli strumenti da riga di comando. Ciò risolve tutti i problemi di estensione sopra menzionati, ma può essere un enorme spreco di spazio, non un'opzione nel codice critico per le prestazioni. Inoltre, l'elaborazione di questi parametri nominati è ora molto più complicata della semplice estrazione di valori da uno stack.
In realtà, i requisiti di spazio possono essere ridotti utilizzando il pool di stringhe, che può ridurre i confronti successivi delle stringhe ai confronti dei puntatori (tranne quando non è possibile garantire che le stringhe statiche siano effettivamente raggruppate, nel qual caso le due stringhe dovranno essere confrontate in dettaglio).
Invece, potremmo anche passare una struttura di dati intelligente che funziona come un dizionario di argomenti con nome. Questo è economico dal lato del chiamante, perché l'insieme di chiavi è staticamente noto nella posizione della chiamata. Ciò consentirebbe di creare una funzione hash perfetta o di precalcolare un trie. La chiamata dovrà comunque verificare l'esistenza di tutti i possibili nomi di parametri che è alquanto costoso. Qualcosa del genere è usato da Python.
Quindi è troppo costoso nella maggior parte dei casi
Se una funzione con parametri nominati deve essere correttamente estensibile, non è possibile ipotizzare un ordine definitivo. Quindi ci sono solo due soluzioni:
- Rendere l'ordine dei parametri nominati parte della firma e non consentire modifiche successive. Ciò è utile per il codice auto-documentante, ma non aiuta con argomenti opzionali.
- Passare una struttura di dati valore-chiave al chiamante, che deve quindi estrarre informazioni utili. Questo è molto costoso in confronto, e di solito si vede solo nei linguaggi di scripting senza enfasi sulle prestazioni.
Altre insidie
I nomi delle variabili in una dichiarazione di funzione di solito hanno un significato interno e non fanno parte dell'interfaccia, anche se molti strumenti di documentazione li mostreranno comunque. In molti casi vorresti nomi diversi per una variabile interna e l'argomento denominato corrispondente. Le lingue che non consentono di scegliere i nomi visibili esternamente di un parametro denominato non ne ottengono molto se il nome della variabile non viene utilizzato tenendo presente il contesto chiamante.
Un problema con le emulazioni degli argomenti denominati è la mancanza di controllo statico sul lato chiamante. Questo è particolarmente facile da dimenticare quando si passa un dizionario di argomenti (ti guarda, Python). Questo è importante perché il superamento di un dizionario è una soluzione comune, ad esempio in JavaScript: foo({bar: "baz", qux: 42})
. Qui, né i tipi di valori né l'esistenza o l'assenza di determinati nomi possono essere controllati staticamente.
Emulazione di parametri nominati (in linguaggi tipicamente statici)
Usare semplicemente stringhe come chiavi e qualsiasi oggetto come valore non è molto utile in presenza di un controllo di tipo statico. Tuttavia, gli argomenti con nome possono essere emulati con strutture o letterali di oggetti:
// Java
static abstract class Arguments {
public String bar = "default";
public int qux = 0;
}
void foo(Arguments args) {
...
}
/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});