Un langage basé sur la limitation de la quantité d'arguments passés aux fonctions

16

L'idée est inspirée du fait que les opérateurs tels que +, -,%, etc. peuvent être considérés comme des fonctions avec un ou deux arguments passés et sans effets secondaires. En supposant que moi ou quelqu'un d'autre écrive un langage qui empêche la transmission de plus de deux arguments et ne fonctionne que via la valeur de retour:

a) un tel langage conduirait-il à un code plus facile à comprendre?

b) le flux du code serait-il plus clair? (forcé à plus d'étapes, avec potentiellement moins d'interactions cachées)

c) les restrictions rendraient-elles le langage excessivement volumineux pour des programmes plus complexes.

d) (bonus) tout autre commentaire sur le pour / le contre

Remarque:

Deux décisions devraient encore être prises - la première est de savoir si autoriser l'entrée utilisateur en dehors de main () ou son équivalent, et aussi quelle sera la règle concernant ce qui se passe lors du passage de tableaux / structures. Par exemple, si quelqu'un veut qu'une seule fonction ajoute plusieurs valeurs, il peut contourner la limitation en la regroupant dans un tableau. Cela pourrait être arrêté en n'autorisant pas un tableau ou une structure à interagir avec lui-même, ce qui vous permettrait toujours, par exemple, de diviser chaque nombre par un montant différent, selon sa position.


la source
4
Salut. Les listes des avantages et des inconvénients ont tendance à donner de mauvaises réponses. Existe-t-il un moyen de reformuler votre question pour obtenir toujours les informations dont vous avez besoin mais dans un autre format?
MetaFight
22
Votre raisonnement ne commence même pas à avoir un sens pour moi. Certaines fonctions ont peu d'arguments donc limitons toutes les fonctions? Normalement, quand on propose des restrictions arbitraires, il y a une raison, quelque chose à gagner. Je ne vois pas ce que cela pourrait vous apporter.
2
Non pas qu'il y ait quelque chose d'intrinsèquement mauvais avec les questions `` et si '' (bien qu'il soit parfois difficile de répondre comme l'a dit @MetaFight), mais si même vous, qui avez pensé à la chose et avez pris soin de poser une question, ne pouvez pas vraiment nommer un bénéficier, alors je suis assez certain que ma réaction initiale de "quoi? non! c'est stupide pourquoi feriez-vous cela" est exacte.
6
Il existe de nombreux langages qui n'autorisent qu'un seul argument par fonction: tout ce qui est basé sur le calcul lambda. Le résultat est généralement une fonction qui prend un seul argument de la liste, ou une fonction renvoyant une fonction qui prend l'argument suivant jusqu'à ce que tous les arguments ont été traités: result = f(a)(b)…(z). C'est le cas dans la famille de langages ML comme Haskell, mais aussi conceptuellement dans d'autres langages comme Lisp, JavaScript ou Perl.
amon
3
@Orangesandlemons: D'accord, alors je peux encoder un nombre arbitraire d'entiers dans un seul entier en utilisant simplement la multiplication et l'addition (pour l'encodage) et la division et la soustraction (pour le décodage). Donc, vous devez également interdire les entiers, ou au moins la multiplication, l'addition, la division et la soustraction. (Une conséquence de la puissance de la programmation est que vous pouvez encoder presque n'importe quoi en utilisant presque n'importe quoi, et donc restreindre les choses est vraiment, vraiment difficile. En général, les restrictions ne "restreignent" rien, elles ennuient simplement les programmeurs.)
Jörg W Mittag

Réponses:

40

Robert C. Martin dans son livre "Clean Code" recommande fortement l'utilisation de fonctions avec 0, 1 ou 2 paramètres au maximum, donc au moins un auteur de livre expérimenté pense que le code devient plus propre en utilisant ce style (cependant, il est sûrement pas l'autorité ultime ici, et ses opinions sont discutables).

Là où Bob Martin est correct à mon humble avis: les fonctions avec 3 paramètres ou plus sont souvent des indicateurs d'une odeur de code. Dans de nombreux cas, les paramètres peuvent être regroupés pour former un type de données combiné, dans d'autres cas, cela peut être un indicateur pour la fonction qui en fait simplement trop.

Cependant, je ne pense pas que ce serait une bonne idée d'inventer un nouveau langage pour cela:

  • si vous voulez vraiment appliquer une telle règle dans tout votre code, vous avez juste besoin d'un outil d'analyse de code pour un langage existant, pas besoin d'inventer un tout nouveau langage pour cela (par exemple, pour C # quelque chose comme 'fxcop' pourrait probablement être utilisé ).

  • Parfois, combiner des paramètres à un nouveau type ne semble pas valoir la peine, ou cela deviendrait une pure combinaison artificielle. Voir, par exemple, cette File.Openméthode du framework .Net. Cela prend quatre paramètres, et je suis presque sûr que les concepteurs de cette API l'ont fait intentionnellement, car ils pensaient que ce serait le moyen le plus pratique de fournir les différents paramètres à la fonction.

  • il existe parfois des scénarios du monde réel où plus de 2 paramètres simplifient les choses pour des raisons techniques (par exemple, lorsque vous avez besoin d'un mappage 1: 1 avec une API existante où vous êtes lié à l'utilisation de types de données simples et ne pouvez pas combiner différents paramètres dans un objet personnalisé)

Doc Brown
la source
16
L'odeur avec plusieurs paramètres est souvent que les différents paramètres vont ensemble. Prenons par exemple le calcul de l'indice de masse corporelle, l'IMC. C'est une fonction de la longueur et du poids d'une personne. f (longueur, poids), mais ces deux paramètres vont vraiment de pair car feriez-vous jamais ce calcul avec la taille d'une personne et le poids d'une autre? Donc, pour mieux représenter cela, vous obtiendrez f (personne) où la personne peut avoir une interface de poids et de longueur.
Pieter B
@PieterB: bien sûr, voir mon montage.
Doc Brown
5
Minuscule et minuscule pinceau de langage: "pourrait indiquer une odeur de code" Une odeur de code n'est-elle pas par définition juste une indication que vous devriez reconsidérer quelque chose, même si vous ne changez finalement pas le code? Une odeur de code ne serait donc pas "indiquée". Si un aspect spécifique indique la possibilité d'un problème, il est une odeur de code. Non?
jpmc26
6
@ jpmc26: D'un autre point de vue, une odeur de code est un problème possible, pas l'indication d'un ;-) Tout dépend de la définition exacte de l'odeur de code (et une fois qu'elle sent, elle a mal tourné, n'est-ce pas?) ?)
hoffmale
3
@PieterB Est-ce que quelqu'un le fait vraiment? Vous devez maintenant créer une nouvelle instance Person chaque fois que vous souhaitez simplement calculer un IMC avec deux valeurs arbitraires. Bien sûr, si votre application utilise déjà des personnes pour commencer et que vous vous trouvez souvent à faire quelque chose comme f (personne.longueur, personne.hauteur), vous pouvez le nettoyer un peu, mais créer de nouveaux objets spécifiquement pour grouper les paramètres semble exagéré.
Lawyerson
47

Il existe de nombreuses langues qui fonctionnent déjà de cette façon, par exemple Haskell. Dans Haskell, chaque fonction prend exactement un argument et renvoie exactement une valeur.

Il est toujours possible de remplacer une fonction qui prend n arguments par une fonction qui prend n-1 arguments et renvoie une fonction qui prend l'argument ultime. En appliquant ceci récursivement, il est toujours possible de remplacer une fonction qui prend un nombre arbitraire d'arguments par une fonction qui prend exactement un argument. Et cette transformation peut être effectuée mécaniquement, par un algorithme.

C'est ce qu'on appelle Frege-Schönfinkeling, Schönfinkeling, Schönfinkel-Currying ou Currying, après Haskell Curry qui l'a longuement étudié dans les années 1950, Moses Schönfinkel, qui l'a décrit en 1924, et Gottlob Frege, qui l'a préfiguré en 1893.

En d'autres termes, la restriction du nombre d'arguments n'a aucun impact.

Jörg W Mittag
la source
2
C'est si vous autorisez le retour des fonctions, bien sûr. Même si ce n'est pas le cas, il vous suffit d'appeler la fonction suivante dans le programme principal. C'est-à-dire que vous pourriez avoir 1 + 1 + 1 où le premier ajout est une fonction qui renvoie une fonction pour un ajout supplémentaire, ou elle peut simplement être appelée deux fois. Ce dernier style serait, en théorie, plus propre, ou je me trompe?
5
"a exactement zéro impact" - La question du PO était de savoir si la lisibilité du code augmenterait ou diminuerait, et je suppose que vous ne prétendez pas qu'une telle décision de conception d'une langue n'a aucun impact là-dessus, n'est-ce pas?
Doc Brown
3
@DocBrown Avec une puissance décente et une surcharge de l'opérateur, je peux prendre un langage curry et le faire ressembler à un langage non durci. Exemple: f *(call_with: a,b,c,d,e) surcharge call_with :pour commencer une chaîne, ,pour étendre la chaîne et *sur le LHS pour l'invoquer fen lui passant chacun des contenus de la chaîne un par un. Un système de surcharge d'opérateur suffisamment faible rend la syntaxe compliquée, mais c'est la faute du système de surcharge d'opérateur plus que tout.
Yakk
En fait, le currying de Haskell réduit une fonction avec n arguments à une fonction avec un argument renvoyant une autre fonction avec n - 1 arguments.
Ryan Reich du
2
@RyanReich Vous avez raison de voir un "fantôme" du "nombre d'arguments" d'une fonction Haskell dans sa signature de type. C'est un fantôme plutôt qu'une vraie signature car en général vous n'avez aucun moyen de savoir si la dernière variable de type dans la signature est également un type de fonction. En tout cas, ce fantôme étant là n'invalide pas le fait que dans Haskell toutes les fonctions prennent 1 argument et retournent soit une valeur de non-fonction soit une autre fonction qui prend également 1 argument. Ceci est intégré dans l'associativité de ->: a-> b-> c est a -> (b-> c). Vous vous trompez donc surtout ici.
Ian
7

J'ai passé du temps ces dernières semaines à essayer d'apprendre le langage informatique J. En J, presque tout est un opérateur, donc vous n'obtenez que des "monades" (fonctions qui n'ont qu'un seul argument) et des "dyades" (fonctions avec exactement deux arguments). Si vous avez besoin de plus d'arguments, vous devez soit les fournir dans un tableau, soit les fournir dans des "boîtes".

J peut être très concis, mais comme son prédécesseur APL, il peut également être très cryptique - mais cela est principalement le résultat de l'objectif du créateur d'émuler la concision mathématique. Il est possible de rendre un programme J plus lisible en utilisant des noms plutôt que des caractères pour créer des opérateurs.

Alpheus
la source
ah, donc cela permet aux tableaux d'interagir avec eux-mêmes.
5

Un langage basé sur la façon dont il contraint le développeur dépend de l'hypothèse que le développeur du langage comprend mieux les besoins de chaque programmeur que le programmeur ne comprend eux-mêmes ces besoins. Il y a des cas où cela est réellement valable. Par exemple, les contraintes sur la programmation multithread nécessitant une synchronisation à l'aide de mutex et de sémaphores sont considérées par beaucoup comme «bonnes» car la plupart des programmeurs ignorent complètement les complexités sous-jacentes spécifiques à la machine que ces contraintes leur cachent. De même, peu souhaitent saisir pleinement les nuances des algorithmes de récupération de place multithread; un langage qui ne vous permet tout simplement pas de casser l'algorithme GC est préféré à celui qui oblige un programmeur à être conscient de trop de nuances.

Vous devriez avoir un argument valable pour expliquer pourquoi, en tant que développeur de langage, vous comprenez l'argument passant tellement mieux que les programmeurs utilisant votre langage qu'il est utile de les empêcher de faire des choses que vous jugez nuisibles. Je pense que ce serait un argument difficile à faire valoir.

Vous devez aussi savoir que les programmeurs vont travailler autour de vos contraintes. S'ils ont besoin de 3 arguments ou plus, ils utiliseront des techniques comme le curry pour les transformer en appels à moins d'arguments. Cependant, cela se fait souvent au détriment de la lisibilité, plutôt que de l'améliorer.

La plupart des langues que je connais avec ce type de règle sont des esolangs, des langues conçues pour démontrer que vous pouvez en effet fonctionner avec un ensemble limité de fonctionnalités. En particulier, les esolangs où chaque caractère est un opcode ont tendance à limiter le nombre d'arguments, simplement parce qu'ils doivent garder la liste des opcodes courte.

Cort Ammon - Rétablir Monica
la source
C'est la meilleure réponse.
Jared Smith
1

Vous aurez besoin de deux choses:

  • Fermeture
  • Type de données composite

J'ajouterai un exemple mathématique pour expliquer la réponse écrite par Jörg W Mittag .

Considérons la fonction gaussienne .

Une fonction gaussienne a deux paramètres pour sa forme, à savoir la moyenne (position centrale de la courbe) et la variance (liée à la largeur d'impulsion de la courbe). En plus des deux paramètres, il faut également fournir la valeur de la variable libre xafin de l'évaluer.

Dans un premier temps, nous concevrons une fonction gaussienne qui prend les trois paramètres, à savoir la moyenne, la variance et la variable libre.

Dans la deuxième étape, nous créons un type de données composite qui combine la moyenne et la variance en une seule chose.

Dans la troisième étape, nous créons une paramétrisation de la fonction gaussienne en créant une fermeture de la fonction gaussienne liée au type de données composite que nous avons créé dans la deuxième étape.

Enfin, nous évaluons la fermeture créée à la troisième étape en lui passant la valeur de la variable libre x.

La structure est donc:

  • Évaluer (calcul)
    • Gaussien paramétré (fermeture: la formule, plus quelques variables liées)
      • GaussianParameters (type de données composite)
        • Valeur moyenne)
        • Écart (valeur)
    • X (la valeur de la variable libre)
rwong
la source
1
  1. Dans à peu près n'importe quel langage de programmation, vous pouvez passer un certain type de liste, tableau, tuple, enregistrement ou objet comme seul argument. Son seul but est de contenir d'autres éléments au lieu de les transmettre individuellement à une fonction. Certains IDE Java ont même une fonction " Extraire un objet de paramètre " pour cela. En interne, Java implémente un nombre variable d'arguments en créant et en passant un tableau.

  2. Si vous voulez vraiment faire ce dont vous parlez sous la forme la plus pure, vous devez regarder le calcul lambda. C'est exactement ce que vous décrivez. Vous pouvez le rechercher sur le Web, mais la description qui me paraissait logique était dans Types et langages de programmation .

  3. Regardez les langages de programmation Haskell et ML (ML est plus simple). Ils sont tous deux basés sur le calcul lambda et n'ont conceptuellement qu'un seul paramètre par fonction (si vous plissez les yeux un peu).

  4. Le point 2 de Josh Bloch est: "Considérez un constructeur face à de nombreux paramètres de constructeur." Vous pouvez voir à quel point cela est détaillé , mais c'est un plaisir de travailler avec une API écrite de cette façon.

  5. Certains langages ont nommé des paramètres, ce qui est une autre approche pour faciliter la navigation dans les énormes signatures de méthode. Kotlin a nommé des arguments par exemple.

GlenPeterson
la source