Dans de nombreux langages, la syntaxe function_name(arg1, arg2, ...)
est utilisée pour appeler une fonction. Lorsque nous voulons appeler la fonction sans aucun argument, nous devons faire function_name()
.
Je trouve étrange qu'un compilateur ou un interpréteur de script nécessite ()
Pour réussir à le détecter en tant qu'appel de fonction. Si une variable est connue pour être appelable, pourquoi function_name;
Ne suffirait-il pas?
D'autre part, dans certaines langues, nous pouvons faire: function_name 'test';
Ou même function_name 'first' 'second';
Pour appeler une fonction ou une commande.
Je pense que les parenthèses auraient été meilleures si elles n'avaient été nécessaires que pour déclarer l'ordre de priorité, et à d'autres endroits étaient facultatives. Par exemple, faire if expression == true function_name;
Devrait être aussi valide que if (expression == true) function_name();
.
La chose la plus ennuyeuse à mon avis est de faire 'SOME_STRING'.toLowerCase()
quand clairement aucun argument n'est requis par la fonction prototype. Pourquoi les concepteurs ont-ils décidé de ne pas utiliser le 'SOME_STRING'.lower
Plus simple?
Disclaimer: Ne vous méprenez pas, j'aime la syntaxe en C! ;) Je demande juste si ça pourrait être mieux. L'exigence de ()
Présente-t-elle des avantages en termes de performances ou facilite-t-elle la compréhension du code? Je suis vraiment curieux de savoir quelle est exactement la raison.
Pour les langages qui utilisent fonctions de première classe , il est assez courant que la syntaxe de référence à une fonction soit:
a = object.functionName
tandis que l'acte de appeler cette fonction est:
b = object.functionName()
a
dans l'exemple ci-dessus ferait référence à la fonction ci-dessus (et vous pourriez l'appeler en faisant a()
), tandis que b
contiendrait la valeur de retour de la fonction.
Alors que certaines langues peuvent faire des appels de fonction sans parenthèses, cela peut être déroutant si elles sont appel la fonction, ou simplement référence = à la fonction.
En effet, Scala permet cela, bien qu'il y ait une convention qui est suivie: si la méthode a des effets secondaires, les parenthèses doivent être utilisées quand même.
En tant que rédacteur de compilateur, je trouverais la présence garantie de parenthèses très pratique; Je saurais toujours que c'est un appel de méthode, et je n'aurais pas à construire une bifurcation pour le cas étrange.
En tant que programmeur et lecteur de code, la présence de parenthèses ne laisse aucun doute qu'il s'agit d'un appel de méthode, même si aucun paramètre n'est transmis.
Le passage de paramètres n'est pas la seule caractéristique qui définit un appel de méthode. Pourquoi devrais-je traiter une méthode sans paramètre différemment d'une méthode qui a des paramètres?
Il s'agit en fait d'un hasard assez subtil de choix de syntaxe. Je parlerai des langages fonctionnels, qui sont basés sur le calcul lambda typé.
Dans ces langages, chaque fonction a exactement un argument. Ce que nous considérons souvent comme des "arguments multiples" est en fait un seul paramètre du type de produit. Ainsi, par exemple, la fonction qui compare deux entiers:
leq : int * int -> bool
leq (a, b) = a <= b
prend une seule paire d'entiers. Les parenthèses ne pas désignent les paramètres de la fonction; ils sont utilisés pour pattern-match l'argument. Pour vous convaincre que c'est vraiment un argument, nous pouvons plutôt utiliser les fonctions projectives au lieu de la correspondance de motifs pour déconstruire la paire:
leq some_pair = some_pair.1 <= some_pair.2
Ainsi, les parenthèses sont vraiment une commodité qui nous permet de faire correspondre les motifs et de sauvegarder certains caractères. Ils ne sont pas obligatoires.
Qu'en est-il d'une fonction qui n'a apparemment aucun argument? Une telle fonction a en fait le domaine Unit
. Le membre unique de Unit
est généralement écrit comme ()
, C'est pourquoi les parenthèses apparaissent.
say_hi : Unit -> string
say_hi a = "Hi buddy!"
Pour appeler cette fonction, il faudrait l'appliquer à une valeur de type Unit
, qui doit être ()
, Donc on finit par écrire say_hi ()
.
Donc, il n'y a vraiment pas de liste d'arguments!
En Javascript par exemple, l'utilisation d'un nom de méthode sans () renvoie la fonction elle-même sans l'exécuter. De cette façon, vous pouvez par exemple passer la fonction en argument à une autre méthode.
Dans Java le choix a été fait qu'un identifiant suivi de () ou (...) signifie un appel de méthode tandis qu'un identifiant sans () fait référence à une variable membre. Cela pourrait améliorer la lisibilité, comme vous n'avez aucun doute si vous traitez avec une méthode ou une variable membre. En fait, le même nom peut être utilisé à la fois pour une méthode et une variable membre, les deux seront accessibles avec leur syntaxe respective.
La syntaxe suit la sémantique, alors commençons par la sémantique:
Quelles sont les façons d'utiliser une fonction ou une méthode?
Il existe en fait plusieurs façons:
Avoir une syntaxe similaire pour différentes utilisations est le meilleur moyen de créer un langage ambigu ou tout au moins un langage déroutant (et nous en avons assez).
Dans les langages C et similaires:
func()
&func
(C création d'un pointeur de fonction)someVariable::someMethod
par exemple (limité au récepteur de méthode, mais toujours utile)Notez comment chaque utilisation présente une syntaxe différente, vous permettant de les distinguer facilement.
Aucune des autres réponses n'a tenté de répondre à la question: quelle redondance devrait-il y avoir dans la conception d'une langue? Parce que même si vous pouvez concevoir une langue pour que x = sqrt y
définit x à la racine carrée de y, cela ne signifie pas nécessairement que vous devriez.
Dans une langue sans redondance, chaque séquence de caractères signifie quelque chose, ce qui signifie que si vous faites une seule erreur, vous n'obtiendrez pas de message d'erreur, votre programme fera la mauvaise chose, ce qui pourrait être quelque chose de très différent de ce que vous destiné et très difficile à déboguer. (Comme le savent tous ceux qui ont travaillé avec des expressions régulières.) Une petite redondance est une bonne chose car elle permet de détecter bon nombre de vos erreurs, et plus il y a de redondance, plus il est probable que les diagnostics seront précis. Maintenant, bien sûr, vous pouvez aller trop loin (aucun de nous ne veut écrire en COBOL de nos jours), mais il y a un équilibre qui est juste.
La redondance améliore également la lisibilité, car il existe davantage d'indices. L'anglais sans redondance serait très difficile à lire, et il en va de même pour les langages de programmation.
Dans une langue avec des effets secondaires, il est vraiment utile que l'OMI fasse la différence entre (en théorie sans effet secondaire) la lecture d'une variable
variable
et appeler une fonction, ce qui pourrait entraîner des effets secondaires
launch_nukes()
OTOH, s'il n'y a pas d'effets secondaires (autres que ceux encodés dans le système de type), alors il n'y a même pas de raison de faire la différence entre lire une variable et appeler une fonction sans arguments.
Dans la plupart des cas, ce sont des choix syntaxiques de la grammaire de la langue. Il est utile pour la grammaire que les différentes constructions individuelles soient (relativement) sans ambiguïté lorsqu'elles sont prises ensemble. (S'il y a des ambiguïtés, comme dans certaines déclarations C++, il doit y avoir des règles spécifiques pour la résolution.) Le compilateur n'a pas la latitude de deviner; il est nécessaire de suivre les spécifications de la langue.
Visual Basic, sous diverses formes, différencie les procédures qui ne renvoient pas de valeur et les fonctions.
Les procédures doivent être appelées comme une instruction et ne nécessitent pas de parens, juste des arguments séparés par des virgules, le cas échéant. Les fonctions doivent être appelées dans le cadre d'une expression et nécessitent des parens.
C'est une distinction relativement inutile qui rend la refactorisation manuelle entre les deux formes plus douloureuse qu'elle ne doit l'être.
(D'un autre côté, Visual Basic utilise les mêmes parens ()
Pour les références de tableau que pour les appels de fonction, donc les références de tableau ressemblent à des appels de fonction. Et cela facilite la refactorisation manuelle d'un tableau en appel de fonction! Donc, nous pourrions méditez sur les autres langues utilisez []
pour les références de tableaux, mais je m'éloigne ...
Dans les langages C et C++, le contenu des variables est automatiquement accédé en utilisant leur nom, et si vous voulez faire référence à la variable elle-même au lieu de son contenu, vous appliquez l'opérateur unaire &
.
Ce type de mécanisme pourrait également être appliqué aux noms de fonction. Le nom brut de la fonction pourrait impliquer un appel de fonction, tandis qu'un opérateur unaire &
Serait utilisé pour faire référence à la fonction (elle-même) en tant que données. Personnellement, j'aime l'idée d'accéder à des fonctions sans argument sans effet secondaire avec la même syntaxe que les variables.
C'est parfaitement plausible (tout comme d'autres choix syntaxiques pour cela).
Cohérence et lisibilité.
Si j'apprends que vous appelez la fonction X
comme ceci: X(arg1, arg2, ...)
, alors je m'attends à ce qu'elle fonctionne de la même manière pour aucun argument: X()
.
Maintenant, en même temps, j'apprends que vous pouvez définir la variable comme un symbole et l'utiliser comme ceci:
a = 5
b = a
...
Maintenant, que penserais-je quand je trouverais cela?
c = X
Votre supposition est aussi bonne que la mienne. Dans des circonstances normales, le X
est une variable. Mais si nous prenions votre chemin, cela pourrait aussi être une fonction! Dois-je me rappeler si quel symbole correspond à quel groupe (variables/fonctions)?
Nous pourrions imposer des contraintes artificielles, par exemple "Les fonctions commencent par une lettre majuscule. Les variables commencent par une lettre minuscule", mais cela n'est pas nécessaire et rend les choses plus compliquées, bien que cela puisse parfois aider certains des objectifs de conception.
Note 1: D'autres réponses ignorent complètement une dernière chose: le langage peut vous permettre d'utiliser le même nom pour la fonction et la variable, et de les distinguer par contexte. Voir Common LISP
par exemple. La fonction x
et la variable x
coexistent parfaitement bien.
Note 2: La réponse acceptée nous montre la syntaxe: object.functionname
. Premièrement, ce n'est pas universel aux langages avec des fonctions de première classe. Deuxièmement, en tant que programmeur, je traiterais cela comme une information supplémentaire: functionname
appartient à un object
. Que object
soit un objet, une classe ou un espace de noms importe peu, mais cela me dit qu'il appartient quelque part. Cela signifie que vous devez soit ajouter une syntaxe artificielle object.
pour chaque fonction globale ou créez un object
pour contenir toutes les fonctions globales.
Et de toute façon, vous perdez la possibilité d'avoir des espaces de noms séparés pour les fonctions et les variables.
Pour ajouter aux autres réponses, prenez cet exemple C:
void *func_factory(void)
{
return 0;
}
void *(*ff)(void);
void example()
{
ff = func_factory;
ff = func_factory();
}
Si l'opérateur d'invocation était facultatif, il n'y aurait aucun moyen de distinguer l'attribution de fonction et l'appel de fonction.
Cela est encore plus problématique dans les langues dépourvues de système de type, par ex. JavaScript, où l'inférence de type ne peut pas être utilisée pour déterminer ce qui est et n'est pas une fonction.