Sintassi Design - Perché utilizzare le parentesi quando non vengono passati argomenti?

63

In molte lingue, la sintassi function_name(arg1, arg2, ...) viene utilizzata per chiamare una funzione. Quando vogliamo chiamare la funzione senza argomenti, dobbiamo fare function_name() .

Trovo strano che un compilatore o un interprete di script richieda () per rilevarlo correttamente come una chiamata di funzione. Se una variabile è conosciuta per essere callable, perché function_name; non dovrebbe essere sufficiente?

D'altra parte, in alcune lingue possiamo fare: function_name 'test'; o anche function_name 'first' 'second'; per chiamare una funzione o un comando.

Penso che le parentesi sarebbero state migliori se fossero state necessarie solo per dichiarare l'ordine di priorità, e in altri luoghi erano facoltative. Ad esempio, facendo if expression == true function_name; dovrebbe essere valido come if (expression == true) function_name(); .

La cosa più fastidiosa a mio avviso è fare 'SOME_STRING'.toLowerCase() quando chiaramente non sono necessari argomenti dalla funzione prototipo. Perché i progettisti hanno deciso contro il più semplice 'SOME_STRING'.lower ?

Disclaimer: Non fraintendermi, adoro la sintassi simile a C! ;) Sto solo chiedendo se potrebbe essere migliore. Richiede () ha dei vantaggi in termini di prestazioni, o semplifica la comprensione del codice? Sono davvero curioso di sapere quale sia esattamente la ragione.

    
posta DRS David Soft 05.10.2016 - 15:49
fonte

10 risposte

250

Per le lingue che utilizzano funzioni di prima classe , è abbastanza comune che la sintassi di che fa riferimento in una funzione è:

a = object.functionName

mentre l'atto di che chiama quella funzione è:

b = object.functionName()

a nell'esempio precedente potrebbe fare riferimento alla funzione sopra (e potresti chiamarla facendo a() ), mentre b conterrà il valore di ritorno della funzione.

Mentre alcune lingue possono eseguire chiamate di funzione senza parentesi, possono confondersi se chiamano la funzione, o semplicemente rimandando alla funzione.

    
risposta data 05.10.2016 - 16:15
fonte
63

In effetti, Scala lo consente, anche se c'è una convenzione che viene seguita: se il metodo ha effetti collaterali, le parentesi dovrebbero essere comunque utilizzate.

Come scrittore di compilatori, troverei la presenza garantita di parentesi piuttosto comoda; Saprei sempre che è una chiamata al metodo e non dovrei costruire una biforcazione per il caso strano.

Come programmatore e lettore di codice, la presenza di parentesi non lascia dubbi sul fatto che si tratti di una chiamata al metodo, anche se non vengono passati parametri.

Il passaggio dei parametri non è l'unica caratteristica che definisce una chiamata al metodo. Perché dovrei trattare un metodo senza parametro diverso da un metodo che ha parametri?

    
risposta data 05.10.2016 - 15:54
fonte
19

Questo è in realtà un sottile colpo di fortuna di scelte di sintassi. Parlerò con linguaggi funzionali, che sono basati sul calcolo lambda tipizzato.

In tali lingue, ogni funzione ha esattamente un argomento . Quello che spesso pensiamo come "argomenti multipli" è in realtà un singolo parametro del tipo di prodotto. Ad esempio, la funzione che confronta due numeri interi:

leq : int * int -> bool
leq (a, b) = a <= b

prende una singola coppia di numeri interi. Le parentesi non indicano i parametri della funzione; sono usati per pattern-match l'argomento. Per convincerti che questo è davvero un argomento, possiamo invece utilizzare le funzioni proiettive invece della corrispondenza del modello per decostruire la coppia:

leq some_pair = some_pair.1 <= some_pair.2

Quindi, le parentesi sono davvero una comodità che ci consente di modellare la corrispondenza e di salvare un po 'di digitazione. Non sono richiesti.

Che dire di una funzione che apparentemente non ha argomenti? Tale funzione ha effettivamente dominio Unit . Il singolo membro di Unit viene solitamente scritto come () , ecco perché vengono visualizzate le parentesi.

say_hi : Unit -> string
say_hi a = "Hi buddy!"

Per chiamare questa funzione, dovremmo applicarla a un valore di tipo Unit , che deve essere () , quindi finiamo per scrivere say_hi () .

Quindi, non esiste davvero una lista di argomenti!

    
risposta data 05.10.2016 - 16:45
fonte
14

In Javascript, per esempio, usando un nome di metodo senza () restituisce la funzione stessa senza eseguirla. In questo modo puoi ad esempio passare la funzione come argomento ad un altro metodo.

In Java si è scelto che un identificatore seguito da () o (...) significhi una chiamata al metodo mentre un identificatore senza () si riferisce a una variabile membro. Ciò potrebbe migliorare la leggibilità, dal momento che non hai dubbi che hai a che fare con un metodo o una variabile membro. Infatti, lo stesso nome può essere usato sia per un metodo che per una variabile membro, entrambi saranno accessibili con la rispettiva sintassi.

    
risposta data 05.10.2016 - 16:10
fonte
10

La sintassi segue la semantica, quindi partiamo dalla semantica:

What are the ways of using a function or method?

Ci sono, in realtà, più modi:

  • si può chiamare una funzione, con o senza argomenti
  • una funzione può essere considerata come un valore
  • una funzione può essere parzialmente applicata (creando una chiusura tenendo almeno un argomento in meno e chiudendo gli argomenti passati)

Avere una sintassi simile per usi diversi è il modo migliore per creare un linguaggio ambiguo o per lo meno confuso (e ne abbiamo abbastanza).

In C e lingue simili a C:

  • il richiamo di una funzione viene eseguito utilizzando le parentesi per racchiudere gli argomenti (potrebbe non esserci nessuno) come in func()
  • trattando una funzione come valore si può fare semplicemente usando il suo nome come in &func (C creando un puntatore a funzione)
  • in alcune lingue, hai sintassi a mano breve per un'applicazione parziale; Java consente ad esempio someVariable::someMethod (limitato al ricevitore del metodo, ma comunque utile)

Nota come ogni utilizzo presenta una sintassi diversa, che ti consente di distinguerli facilmente.

    
risposta data 05.10.2016 - 16:51
fonte
8

In una lingua con effetti collaterali è IMO davvero utile per distinguere tra (in teoria libera da effetti collaterali) la lettura di una variabile

variable

e chiamando una funzione, che potrebbe incorrere in effetti collaterali

launch_nukes()

OTOH, se non ci sono effetti collaterali (diversi da quelli codificati nel sistema dei tipi), allora non c'è motivo di distinguere tra leggere una variabile e chiamare una funzione senza argomenti.

    
risposta data 05.10.2016 - 16:25
fonte
7

Nessuna delle altre risposte ha tentato di affrontare la domanda: quanta ridondanza dovrebbe esserci nella progettazione di una lingua? Perché anche se puoi progettare una lingua in modo che x = sqrt y assegni x alla radice quadrata di y, ciò non significa che devi necessariamente.

In una lingua senza ridondanza, ogni sequenza di caratteri significa qualcosa, il che significa che se commetti un singolo errore, non riceverai un messaggio di errore, il tuo programma farà la cosa sbagliata, che potrebbe essere qualcosa di molto diverso da quello che intendevi e molto difficile da eseguire il debug. (Come chiunque saprà lavorare con le espressioni regolari lo saprà.) Una piccola ridondanza è una buona cosa perché consente di rilevare molti dei vostri errori, e maggiore è la ridondanza, più è probabile che la diagnostica sia accurata. Ora, naturalmente, puoi prenderlo troppo lontano (nessuno di noi vuole scrivere in COBOL in questi giorni), ma c'è un equilibrio corretto.

La ridondanza aiuta anche la leggibilità, perché ci sono più indizi. L'inglese senza ridondanza sarebbe molto difficile da leggere, e lo stesso vale per i linguaggi di programmazione.

    
risposta data 06.10.2016 - 23:23
fonte
5

Nella maggior parte dei casi, queste sono scelte sintattiche della grammatica della lingua. È utile per la grammatica che i vari costrutti individuali siano (relativamente) non ambigui quando vengono presi tutti insieme. (Se ci sono ambiguità, come in alcune dichiarazioni C ++, ci devono essere regole specifiche per la risoluzione.) Il compilatore non ha la latitudine per indovinare; è necessario seguire le specifiche della lingua.

Visual Basic, in varie forme, distingue tra procedure che non restituiscono un valore e funzioni.

Le procedure devono essere chiamate come una dichiarazione e non richiedono parenti, solo gli argomenti separati da virgole, se presenti. Le funzioni devono essere chiamate come parte di un'espressione e richiedono il paren.

È una distinzione relativamente inutile che rende il refactoring manuale tra le due forme più doloroso di quanto non debba essere.

(D'altra parte Visual Basic usa lo stesso paren () per i riferimenti di matrice come per le chiamate di funzione, quindi i riferimenti di matrice assomigliano alle chiamate di funzione e questo facilita il refactoring manuale di un array in una chiamata di funzione! medita su altre lingue usa [] per i riferimenti di array, ma divido ...)

Nei linguaggi C e C ++ il contenuto delle variabili è automaticamente accessibile usando il loro nome e, se si desidera fare riferimento alla variabile stessa anziché al suo contenuto, si applica l'operatore unario & .

Questo tipo di meccanismo potrebbe anche essere applicato ai nomi di funzioni. Il nome della funzione grezza potrebbe implicare una chiamata di funzione, mentre un operatore unario & verrebbe utilizzato per riferirsi alla funzione (stesso) come dati. Personalmente, mi piace l'idea di accedere a funzioni senza argomenti prive di effetti collaterali con la stessa sintassi delle variabili.

Questo è perfettamente plausibile (come lo sono altre scelte sintattiche per questo).

    
risposta data 05.10.2016 - 16:58
fonte
4

Per aggiungere alle altre risposte, prendi questo esempio in C:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

Se l'operatore di chiamata era facoltativo, non ci sarebbe modo di distinguere tra l'assegnazione della funzione e la chiamata alla funzione.

Questo è ancora più problematico nelle lingue prive di un sistema di tipi, ad es. JavaScript, dove l'inferenza di tipo non può essere utilizzata per capire cosa è e non è una funzione.

    
risposta data 05.10.2016 - 23:22
fonte
4

Coerenza e leggibilità.

Se imparo che chiami la funzione X come questa: X(arg1, arg2, ...) , allora mi aspetto che funzioni allo stesso modo per nessun argomento: X() .

Ora allo stesso tempo imparo che puoi definire la variabile come un simbolo e usarla in questo modo:

a = 5
b = a
...

Ora cosa dovrei pensare quando trovo questo?

c = X

La tua ipotesi è buona come la mia. In circostanze normali, X è una variabile. Ma se dovessimo seguire la tua strada, potrebbe anche essere una funzione! Devo ricordare se quale simbolo associa a quale gruppo (variabili / funzioni)?

Potremmo imporre vincoli artificiali, ad es. "Le funzioni iniziano con la lettera maiuscola, le variabili iniziano con la lettera minuscola", ma ciò non è necessario e rende le cose più complicate, anche se a volte potrebbero aiutare alcuni degli obiettivi di progettazione.

Nota a margine 1: Altre risposte ignorano completamente un'altra cosa: la lingua potrebbe consentire di usare lo stesso nome per entrambe le funzioni e le variabili e distinguere per contesto. Vedere Common Lisp come esempio. Funzione x e variabile x coesistono perfettamente bene.

Side-note 2: la risposta accettata ci mostra la sintassi: object.functionname . Innanzitutto, non è universale alle lingue con funzioni di prima classe. In secondo luogo, come programmatore considererei questa informazione aggiuntiva: functionname appartiene a object . Se object è un oggetto, una classe o uno spazio dei nomi non importa molto, ma mi dice che appartiene da qualche parte . Ciò significa che devi aggiungere sintassi artificiale object. per ogni funzione globale o creare un po 'di object per contenere tutte le funzioni globali.

In ogni caso, perdi la possibilità di avere spazi dei nomi separati per funzioni e variabili.

    
risposta data 06.10.2016 - 11:01
fonte

Leggi altre domande sui tag