Perché la maggior parte dei linguaggi di programmazione ha una parola chiave o una sintassi speciali per dichiarare le funzioni? [chiuso]


39

La maggior parte dei linguaggi di programmazione (sia linguaggi dinamici che statici) hanno parole chiave e / o sintassi speciali che sembrano molto diverse dalla dichiarazione delle variabili per la dichiarazione delle funzioni. Vedo le funzioni come dichiarare un'altra entità denominata:

Ad esempio in Python:

x = 2
y = addOne(x)
def addOne(number): 
  return number + 1

Perchè no:

x = 2
y = addOne(x)
addOne = (number) => 
  return number + 1

Allo stesso modo, in una lingua come Java:

int x = 2;
int y = addOne(x);

int addOne(int x) {
  return x + 1;
}

Perchè no:

int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
  return x + 1;
}

Questa sintassi sembra un modo più naturale di dichiarare qualcosa (sia esso una funzione o una variabile) e una parola chiave in meno come defo functionin alcune lingue. E, IMO, è più coerente (guardo nello stesso posto per capire il tipo di una variabile o funzione) e probabilmente rende il parser / grammatica un po 'più semplice da scrivere.

So che pochissimi linguaggi usano questa idea (CoffeeScript, Haskell) ma i linguaggi più comuni hanno una sintassi speciale per le funzioni (Java, C ++, Python, JavaScript, C #, PHP, Ruby).

Anche in Scala, che supporta entrambi i modi (e ha l'inferenza del tipo), è più comune scrivere:

def addOne(x: Int) = x + 1

Piuttosto che:

val addOne = (x: Int) => x + 1

IMO, almeno a Scala, questa è probabilmente la versione più facilmente comprensibile ma questo idioma è raramente seguito:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

Sto lavorando sul mio linguaggio giocattolo e mi chiedo se ci sono delle insidie ​​se progetto la mia lingua in modo tale e se ci sono ragioni storiche o tecniche questo modello non è ampiamente seguito?


9
Il motivo è probabilmente storico. Chiunque lo facesse per primo lo fece in quel modo, e tutti gli altri lo copiarono. Ma dubito che possiamo saperlo con certezza.

29
Penso che sia perché una funzione o un metodo semplicemente non è solo un'altra entità denominata . Nei linguaggi funzionali sono (puoi passarli in giro ecc.) Ma in altri linguaggi (come Java) una funzione o un metodo è qualcosa di completamente diverso da una semplice variabile e non può essere trattato come tale (ammetto che Java 8 ne indebolisce istruzione), quindi ha senso definire funzioni / metodi in modo diverso, poiché si comportano diversamente.
11684,

15
Non sono d'accordo sul fatto che la tua proposta sia un modo più naturale di dichiarare le funzioni. Non mi piace molto la sintassi di Coffeescript e parte di ciò ha a che fare con la sintassi della sua dichiarazione di funzione. Il fatto è che se non è rotto non aggiustarlo. Scrivere "def" o "funzione" non è un grosso problema ed è molto più evidente uno sguardo rispetto ad alcuni fantasiosi simboli del Perl che con gli occhi impensati o stanchi potrebbero facilmente essere scambiati per qualcos'altro. Su una nota completamente personale, penso anche che la tua sintassi suggerita negli esempi sembri molto più brutta della sintassi corrente.
Roy,

22
In che modo '=>' non è una "parola chiave o sintassi speciale per dichiarare una funzione"?
TML,

11
Non so te, ma per me (int => int) addOne = (x) => {è molto più "speciale" e "complesso" di int addOne(int) {...
Bogdan Alexandru,

Risposte:


48

Penso che il motivo sia che le lingue più popolari provengono o sono state influenzate dalla famiglia di lingue C rispetto alle lingue funzionali e alla loro radice, il calcolo lambda.

E in queste lingue, le funzioni non sono solo un altro valore:

  • In C ++, C # e Java, puoi sovraccaricare le funzioni: puoi avere due funzioni con lo stesso nome, ma con firma diversa.
  • In C, C ++, C # e Java, è possibile avere valori che rappresentano funzioni, ma i puntatori di funzione, i funzioni, i delegati e le interfacce funzionali sono tutti distinti dalle funzioni stesse. Parte del motivo è che la maggior parte di quelli non sono in realtà solo funzioni, sono una funzione insieme ad alcuni stati (mutabili).
  • Le variabili sono mutabili di default (è necessario utilizzare const, readonlyo finaldi vietare mutazione), ma le funzioni non possono essere riassegnati.
  • Da una prospettiva più tecnica, il codice (che è composto da funzioni) e i dati sono separati. In genere occupano diverse parti della memoria e vi si accede in modo diverso: il codice viene caricato una volta e quindi eseguito solo (ma non letto o scritto), mentre i dati vengono spesso allocati e deallocati costantemente e vengono scritti e letti, ma mai eseguiti.

    E poiché C doveva essere "vicino al metal", ha senso rispecchiare questa distinzione anche nella sintassi del linguaggio.

  • L'approccio "funzione è solo un valore" che costituisce la base della programmazione funzionale ha acquisito trazione nei linguaggi comuni solo relativamente di recente, come dimostra l'introduzione tardiva di lambda in C ++, C # e Java (2011, 2007, 2014).


12
C # aveva funzioni anonime - che sono solo lambda senza deduzione del tipo e una sintassi goffa - dal 2005.
Eric Lippert,

2
"Da un punto di vista più tecnico, il codice (che è composto da funzioni) e i dati sono separati" - alcune lingue, i LISP consapevolmente non fanno questa separazione e non trattano in modo troppo diverso codice e dati. (I LISP sono l'esempio più noto, ma ci sono un sacco di altre lingue come REBOL che lo fanno)
Benjamin Gruenbaum,

1
@BenjaminGruenbaum Stavo parlando del codice compilato in memoria, non del livello linguistico.
svick,

C ha puntatori a funzione che, almeno leggermente, possono essere considerati solo un altro valore. Un valore pericoloso con cui scherzare per essere sicuri, ma sono successe cose più strane.
Patrick Hughes,

@PatrickHughes Sì, li menziono nel mio secondo punto. Ma i puntatori a funzione sono abbastanza diversi dalle funzioni.
svick,

59

È perché è importante che gli umani riconoscano che le funzioni non sono solo "un'altra entità nominata". A volte ha senso manipolarli come tali, ma possono comunque essere riconosciuti a colpo d'occhio.

Non importa cosa pensa il computer della sintassi, dato che una chiazza incomprensibile di personaggi va bene per una macchina da interpretare, ma sarebbe quasi impossibile per gli umani capire e mantenere.

È davvero lo stesso motivo per cui abbiamo loop while e for, switch e if else, ecc., Anche se alla fine tutti si riducono a un'istruzione di confronto e salto. Il motivo è perché è lì a beneficio del mantenimento e della comprensione del codice da parte degli umani.

Avere le tue funzioni come "un'altra entità denominata" nel modo in cui stai proponendo renderà il tuo codice più difficile da vedere, e quindi più difficile da capire.


12
Non sono d'accordo che il trattamento funziona come entità con nome necessariamente rende il codice più difficile da capire. Ciò è quasi certamente vero per qualsiasi codice che segue un paradigma procedurale, ma potrebbe non essere vero per il codice che segue un paradigma funzionale.
Kyle Strand,

29
"È davvero lo stesso motivo per cui abbiamo loop di tempo e di ciclo, switch e se altro, ecc., Anche se alla fine tutti si riducono a un'istruzione di confronto e salto. " +1 a quello. Se tutti i programmatori fossero a proprio agio con il linguaggio macchina, non avremmo bisogno di tutti questi linguaggi di programmazione fantasiosi (alto o basso livello). Ma la verità è che ognuno ha un livello di comfort diverso da quanto vicino alla macchina vuole scrivere il proprio codice. Una volta trovato il tuo livello, devi solo attenerti alla sintassi che ti è stata fornita (o scrivere le tue specifiche di lingua e il compilatore se sei davvero disturbato).
Hoki,

12
+1. Una versione più concisa potrebbe essere "Perché tutti i linguaggi naturali distinguono i nomi dai verbi?"
msw,

2
"una serie incomprensibile di personaggi va bene per una macchina da interpretare, ma sarebbe quasi impossibile per gli umani capire e mantenere" Che qualifica avventata per un cambiamento sintattico così modesto come proposto nella domanda. Uno si adatta rapidamente alla sintassi. Questa proposta è un allontanamento molto meno radicale dalla convenzione di quanto il metodo OO chiamasse sintassi object.method (args) era ai suoi tempi, eppure quest'ultimo non si è dimostrato pressoché impossibile da comprendere e mantenere per l'uomo. Nonostante il fatto che nella maggior parte dei linguaggi OO i metodi non debbano essere considerati come archiviati in istanze di classe.
Marc van Leeuwen,

4
@MarcvanLeeuwen. Il tuo commento è vero e ha senso, ma un po 'di confusione è provocata dal modo in cui il post originale viene redatto. In realtà ci sono 2 domande: (i) Perché è così? e (ii) Perché non in questo modo invece? . La risposta è whatsisnamestata più indirizzata al primo punto (e avvertendo del pericolo di rimuovere questi fail-safe), mentre il tuo commento è più correlato alla seconda parte della domanda. È davvero possibile cambiare questa sintassi (e come hai già descritto, è già stato fatto molte volte ...) ma non andrà bene a tutti (poiché oop non è adatto a tutti.
Hoki,

10

Potresti essere interessato a sapere che, fin dai tempi preistorici, una lingua chiamata ALGOL 68 utilizzava una sintassi vicina a ciò che proponi. Riconoscendo che gli identificatori di funzione sono associati a valori proprio come lo sono altri identificatori, in quella lingua è possibile dichiarare una funzione (costante) utilizzando la sintassi

nome -tipo funzione = ( elenco parametri ) tipo-risultato : body ;

Concretamente il tuo esempio potrebbe essere letto

PROC (INT)INT add one = (INT n) INT: n+1;

Riconoscendo la ridondanza in quanto il tipo iniziale può essere letto dall'RHS della dichiarazione ed essendo un tipo di funzione che inizia sempre con PROC, questo potrebbe (e di solito) essere contratto

PROC add one = (INT n) INT: n+1;

ma nota che =viene ancora prima dell'elenco dei parametri. Si noti inoltre che se si desidera una variabile di funzione (a cui in seguito potrebbe essere assegnato un altro valore dello stesso tipo di funzione), è =necessario sostituirla con :=, dando uno dei

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

Tuttavia in questo caso entrambe le forme sono in realtà abbreviazioni; poiché l'identificatore func vardesigna un riferimento a una funzione generata localmente, sarebbe la forma completamente espansa

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

Questa particolare forma sintattica è facile da abituare, ma chiaramente non aveva un grande seguito in altri linguaggi di programmazione. Anche i linguaggi di programmazione funzionali come Haskell preferiscono lo stile f n = n+1con = seguente l'elenco dei parametri. Immagino che il motivo sia principalmente psicologico; dopo tutto anche i matematici spesso non preferiscono, come faccio io, f = nn + 1 rispetto a f ( n ) = n + 1.

A proposito, la discussione di cui sopra evidenzia un'importante differenza tra variabili e funzioni: le definizioni delle funzioni di solito associano un nome a un valore di funzione specifico, che non può essere successivamente modificato, mentre le definizioni delle variabili di solito introducono un identificatore con un valore iniziale , ma che può cambiare in seguito. (Non è una regola assoluta; le variabili di funzione e le costanti non funzionali si verificano nella maggior parte delle lingue.) Inoltre, nelle lingue compilate il valore associato in una definizione di funzione è di solito una costante di tempo di compilazione, in modo che le chiamate alla funzione possano essere compilato utilizzando un indirizzo fisso nel codice. In C / C ++ questo è persino un requisito; l'equivalente di ALGOL 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

non può essere scritto in C ++ senza introdurre un puntatore a funzione. Questo tipo di limitazioni specifiche giustifica l'utilizzo di una sintassi diversa per le definizioni delle funzioni. Ma dipendono dalla semantica della lingua e la giustificazione non si applica a tutte le lingue.


1
"I matematici non preferiscono f = nn + 1 rispetto a f ( n ) = n + 1" ... Per non parlare dei fisici, a cui piace scrivere solo f = n + 1 ...
leftaroundabout

1
@leftaroundabout è perché ⟼ è un dolore da scrivere. Onestamente.
Pierre Arlaud,

8

Hai citato Java e Scala come esempi. Tuttavia, hai trascurato un fatto importante: quelle non sono funzioni, quelli sono metodi. I metodi e le funzioni sono sostanzialmente diversi. Le funzioni sono oggetti, i metodi appartengono agli oggetti.

In Scala, che ha sia funzioni che metodi, ci sono le seguenti differenze tra metodi e funzioni:

  • i metodi possono essere generici, le funzioni no
  • i metodi possono avere nessuno, uno o più elenchi di parametri, le funzioni hanno sempre esattamente un elenco di parametri
  • i metodi possono avere un elenco di parametri implicito, le funzioni no
  • i metodi possono avere parametri opzionali con argomenti predefiniti, le funzioni no
  • i metodi possono avere parametri ripetuti, le funzioni no
  • i metodi possono avere parametri per nome, le funzioni no
  • i metodi possono essere chiamati con argomenti denominati, le funzioni no
  • le funzioni sono oggetti, i metodi no

Quindi, la sostituzione proposta semplicemente non funziona, almeno per quei casi.


2
"Le funzioni sono oggetti, i metodi appartengono agli oggetti." Questo dovrebbe applicarsi a tutte le lingue (come C ++)?
svick,

9
"i metodi possono avere parametri per nome, le funzioni no" - questa non è una differenza fondamentale tra metodi e funzioni, è solo una stranezza di Scala. Lo stesso con la maggior parte (tutti?) Degli altri.
user253751,

1
I metodi sono fondamentalmente solo un tipo speciale di funzioni. -1. Nota che Java (e per estensione Scala) non ha altri tipi di funzioni. Le sue "funzioni" sono oggetti con un metodo (in generale le funzioni potrebbero non essere oggetti o persino valori di prima classe, indipendentemente dal fatto che siano dipendenti dalla lingua).
Jan Hudec,

@JanHudec: semanticamente, Java ha sia funzioni che metodi; utilizza la terminologia "metodi statici" quando si fa riferimento a funzioni, ma tale terminologia non cancella la distinzione semantica.
supercat,

3

I motivi che mi vengono in mente sono:

  • È più facile per il compilatore sapere cosa dichiariamo.
  • È importante per noi sapere (in modo banale) se questa è una funzione o una variabile. Le funzioni sono generalmente scatole nere e non ci interessa la loro implementazione interna. Non mi piace l'inferenza del tipo sui tipi restituiti in Scala, perché credo che sia più facile usare una funzione che ha un tipo restituito: è spesso l'unica documentazione fornita.
  • E la più importante è la seguente strategia di folla che viene utilizzata nella progettazione di linguaggi di programmazione. C ++ è stato creato per rubare i programmatori C e Java è stato progettato in modo da non spaventare i programmatori C ++ e C # per attirare programmatori Java. Perfino C #, che penso sia un linguaggio molto moderno con un team straordinario dietro, ha copiato alcuni errori da Java o persino da C.

2
"... e C # per attirare programmatori Java" - questo è discutibile, C # è molto più simile a C ++ che a Java. e a volte sembra che sia stato reso intenzionalmente incompatibile con Java (nessun carattere jolly, nessuna classe interna, nessuna covarianza del tipo di ritorno ...)
Sarge Borsch,

1
@SargeBorsch Non penso che sia stato reso intenzionalmente incompatibile con Java, sono abbastanza sicuro che il team di C # abbia semplicemente provato a farlo bene, o farlo meglio, comunque tu lo voglia guardare. Microsoft ha già scritto il proprio Java e JVM ed è stata citata in giudizio. Quindi il team di C # era probabilmente molto motivato a eclissare Java. È sicuramente incompatibile, e meglio per questo. Java ha iniziato con alcune limitazioni di base e sono contento che il team di C # abbia scelto, ad esempio, di fare generici in modo diverso (visibile nel sistema dei tipi e non solo zucchero).
codenheim,

2

Capovolgendo la domanda, se non si è interessati a provare a modificare il codice sorgente su una macchina che è estremamente limitata dalla RAM, o minimizzare il tempo di leggerlo da un floppy disk, cosa c'è di sbagliato nell'usare le parole chiave?

Certamente è più bello da leggere x=y+zdi store the value of y plus z into x, ma ciò non significa che i caratteri di punteggiatura siano intrinsecamente "migliori" delle parole chiave. Se le variabili i, je ksono Integer, ed xè Real, considera le seguenti righe in Pascal:

k := i div j;
x := i/j;

La prima riga eseguirà una divisione intera troncante, mentre la seconda eseguirà una divisione in numero reale. La distinzione può essere fatta bene perché Pascal usa divcome operatore di divisione di numeri interi troncanti, piuttosto che cercare di usare un segno di punteggiatura che ha già un altro scopo (divisione del numero reale).

Mentre ci sono alcuni contesti in cui può essere utile rendere concisa la definizione di una funzione (ad esempio un lambda che viene usato come parte di un'altra espressione), si suppone che le funzioni si distinguano e siano facilmente riconoscibili visivamente come funzioni. Mentre potrebbe essere possibile rendere la distinzione molto più sottile e usare solo caratteri di punteggiatura, che senso avrebbe? Dire Function Foo(A,B: Integer; C: Real): Stringdice chiaramente qual è il nome della funzione, quali parametri si aspetta e cosa restituisce. Forse uno potrebbe accorciarlo di sei o sette caratteri sostituendolo Functioncon alcuni caratteri di punteggiatura, ma cosa si otterrebbe?

Un'altra cosa da notare è che esiste una maggior parte dei framework una differenza fondamentale tra una dichiarazione che assocerà sempre un nome a un particolare metodo o un particolare bind virtuale e uno che crea una variabile che identifica inizialmente un particolare metodo o bind, ma potrebbe essere modificato in fase di esecuzione per identificarne un altro. Poiché questi sono concetti semanticamente molto diversi nella maggior parte dei quadri procedurali, ha senso che debbano avere una sintassi diversa.


3
Non penso che questa domanda riguardi la concisione. Soprattutto perché ad esempio void f() {}è in realtà più breve dell'equivalente lambda in C ++ ( auto f = [](){};), C # ( Action f = () => {};) e Java ( Runnable f = () -> {};). La concisione di lambda deriva dall'inferenza del tipo e dall'omissione return, ma non penso che sia correlato a ciò che questa domanda pone.
svick,

2

Bene, il motivo potrebbe essere che quelle lingue non sono abbastanza funzionali, per così dire. In altre parole, piuttosto raramente si definiscono le funzioni. Pertanto, l'uso di una parola chiave aggiuntiva è accettabile.

Nelle lingue dell'eredità ML o Miranda, OTOH, le funzioni vengono definite la maggior parte delle volte. Guarda un po 'di codice Haskell, per esempio. È letteralmente principalmente una sequenza di definizioni di funzioni, molte di queste hanno funzioni locali e funzioni locali di quelle funzioni locali. Quindi, una parola chiave divertente in Haskell sarebbe un errore tanto quanto richiedere una dichiarazione di asserzione in una lingua imperativa per iniziare con assegnare . L'assegnazione della causa è probabilmente di gran lunga la frase più frequente.


1

Personalmente, non vedo alcun difetto fatale nella tua idea; potresti scoprire che è più complicato di quanto ti aspetti di esprimere determinate cose usando la tua nuova sintassi e / o potresti scoprire che devi rivederlo (aggiungendo vari casi speciali e altre funzionalità, ecc.), ma dubito che ti troverai bisogno di abbandonare completamente l'idea.

La sintassi che hai proposto assomiglia più o meno a una variante di alcuni stili di notazione talvolta usati per esprimere funzioni o tipi di funzioni in matematica. Ciò significa che, come tutte le grammatiche, probabilmente si rivolge più ad alcuni programmatori che ad altri. (Come matematico, mi piace per caso.)

Tuttavia, dovresti notare che nella maggior parte delle lingue, la defsintassi di stile (cioè la sintassi tradizionale) si comporta diversamente da un'assegnazione variabile standard.

  • Nella famiglia Ce C++, le funzioni non sono generalmente trattate come "oggetti", cioè blocchi di dati digitati da copiare e mettere in pila e quant'altro. (Sì, puoi avere puntatori a funzione, ma quelli puntano ancora al codice eseguibile, non a "dati" nel senso tipico.)
  • Nella maggior parte dei linguaggi OO, esiste una gestione speciale per i metodi (ovvero le funzioni membro); cioè, non sono solo funzioni dichiarate nell'ambito di una definizione di classe. La differenza più importante è che l'oggetto su cui viene chiamato il metodo viene in genere passato come primo parametro implicito al metodo. Python lo rende esplicito con self(che, a proposito, in realtà non è una parola chiave; potresti rendere qualsiasi identificatore valido il primo argomento di un metodo).

Devi considerare se la tua nuova sintassi in modo accurato (e si spera intuitivamente) rappresenta ciò che il compilatore o l'interprete sta effettivamente facendo. Potrebbe aiutare a leggere, diciamo, la differenza tra lambda e metodi in Ruby; questo ti darà un'idea di come le tue funzioni sono solo paradigma di dati differisce dal tipico paradigma OO / procedurale.


1

Per alcune lingue le funzioni non sono valori. In una lingua del genere per dirlo

val f = << i : int  ... >> ;

è una definizione di funzione, mentre

val a = 1 ;

dichiara una costante, confonde perché stai usando una sintassi per significare due cose.

Altre lingue, come ML, Haskell e Scheme, trattano le funzioni come valori di 1a classe, ma forniscono all'utente una sintassi speciale per dichiarare le costanti con valore di funzione. * Stanno applicando la regola che "l'utilizzo accorcia la forma". Vale a dire se un costrutto è sia comune che dettagliato, dovresti dare all'utente una scorciatoia. Non è elegante dare all'utente due diverse sintassi che significano esattamente la stessa cosa; a volte l'eleganza dovrebbe essere sacrificata all'utilità.

Se, nella tua lingua, le funzioni sono di 1a classe, allora perché non provare a trovare una sintassi abbastanza concisa da non essere tentato di trovare uno zucchero sintattico?

-- Modificare --

Un altro problema che nessuno ha sollevato (ancora) è la ricorsione. Se lo permetti

{ 
    val f = << i : int ... g(i-1) ... >> ;
    val g = << j : int ... f(i-1) ... >> ;
    f(x)
}

e tu permetti

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

segue che dovresti permettere

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

In un linguaggio pigro (come Haskell), non c'è problema qui. In una lingua essenzialmente priva di controlli statici (come LISP), qui non c'è nessun problema. Ma in un linguaggio desideroso controllato staticamente, devi stare attento a come sono definite le regole del controllo statico, se vuoi consentire i primi due e vietare l'ultimo.

- Fine modifica -

* Si potrebbe sostenere che Haskell non appartiene a questo elenco. Fornisce due modi per dichiarare una funzione, ma entrambi sono, in un certo senso, generalizzazioni della sintassi per dichiarare costanti di altri tipi


0

Questo potrebbe essere utile su linguaggi dinamici in cui il tipo non è così importante, ma non è leggibile in linguaggi tipizzati statici in cui si desidera sempre conoscere il tipo di variabile. Inoltre, nei linguaggi orientati agli oggetti è abbastanza importante conoscere il tipo di variabile, al fine di sapere quali operazioni supporta.

Nel tuo caso, una funzione con 4 variabili sarebbe:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

Quando guardo l'intestazione della funzione e vedo (x, y, z, s) ma non conosco i tipi di queste variabili. Se voglio sapere il tipo di zquale è il terzo parametro, dovrò guardare all'inizio della funzione e iniziare a contare 1, 2, 3 e poi vedere che il tipo è double. Nel primo modo guardo direttamente e vedo double z.


3
Naturalmente c'è quella cosa meravigliosa chiamata inferenza di tipo, che ti permette di scrivere qualcosa come var addOne = (int x, long y, double z, String s) => { x + 1 }in un linguaggio non moronico tipicamente statico di tua scelta (esempi: C #, C ++, Scala). Anche l'inferenza di tipo locale altrimenti molto limitata usata da C # è completamente sufficiente per questo. Quindi questa risposta sta semplicemente criticando una sintassi specifica che è dubbia in primo luogo e non effettivamente utilizzata da nessuna parte (sebbene la sintassi di Haskell abbia un problema molto simile).
amon,

4
@amon La sua risposta potrebbe criticare la sintassi, ma sottolinea anche che la sintassi intenzionale nella domanda potrebbe non essere "naturale" per tutti.

1
@amon L'utilità dell'inferenza del tipo non è una cosa meravigliosa per tutti. Se leggi il codice più spesso di quello che scrivi, allora l'inferenza del tipo è sbagliata perché devi dedurre in testa il tipo quando leggi il codice; ed è molto più veloce leggere il tipo che pensare effettivamente a quale tipo viene utilizzato.
m3th0dman,

1
Le critiche mi sembrano valide. Per Java, l'OP sembra pensare che [input, output, name, input] sia una funzione def più semplice / più chiara della semplice [output, name, input]? Come afferma @ m3th0dman, con firme più lunghe, l'approccio "più pulito" dell'OP diventa molto lungo.
Michael,

0

C'è una ragione molto semplice per avere una tale distinzione nella maggior parte delle lingue: è necessario distinguere valutazione e dichiarazione . Il tuo esempio è buono: perché non come le variabili? Bene, le espressioni delle variabili vengono immediatamente valutate.

Haskell ha un modello speciale in cui non c'è distinzione tra valutazione e dichiarazione, motivo per cui non è necessaria una parola chiave speciale.


2
Ma qualche sintassi per le funzioni lambda risolve anche questo, quindi perché non usarlo?
svick,

0

Le funzioni sono dichiarate in modo diverso da letterali, oggetti, ecc. Nella maggior parte delle lingue perché sono usate in modo diverso, hanno eseguito il debug in modo diverso e pongono diverse potenziali fonti di errore.

Se un riferimento a un oggetto dinamico o un oggetto mutabile viene passato a una funzione, la funzione può modificare il valore dell'oggetto durante l'esecuzione. Questo tipo di effetto collaterale può rendere difficile seguire cosa farà una funzione se è annidata all'interno di un'espressione complessa, e questo è un problema comune in linguaggi come C ++ e Java.

Prendi in considerazione il debug di una sorta di modulo del kernel in Java, dove ogni oggetto ha un'operazione toString (). Mentre ci si può aspettare che il metodo toString () ripristini l'oggetto, potrebbe essere necessario disassemblare e riassemblare l'oggetto per tradurne il valore in un oggetto String. Se stai provando a eseguire il debug dei metodi che toString () chiamerà (in uno scenario hook-and-template) per fare il suo lavoro ed evidenzia accidentalmente l'oggetto nella finestra delle variabili della maggior parte degli IDE, può causare il crash del debugger. Questo perché l'IDE proverà a toString () l'oggetto che chiama proprio il codice in fase di debug. Nessun valore primitivo fa mai schifo come questo perché il significato semantico dei valori primitivi è definito dalla lingua, non dal programmatore.

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.