Pourquoi tant de langues sont passées par valeur?

36

Même les langues où vous avez une manipulation de pointeur explicite, comme C, sont toujours passées par valeur (vous pouvez les passer par référence mais ce n'est pas le comportement par défaut).

Quel est l'avantage de cela, pourquoi tant de langues sont-elles passées par des valeurs et pourquoi d'autres sont-elles passées par référence ? (Je comprends que Haskell est passé par référence bien que je ne sois pas sûr).

Mon code n'a pas de bugs
la source
5
void acceptEntireProgrammingLanguageByValue(C++);
Thomas Eding
4
Ça pourrait être pire. Certaines anciennes langues permettaient également d' appeler par leur nom
hugomg le
25
En fait, en C, vous ne pouvez pas passer par référence. Vous pouvez passer un pointeur par valeur , ce qui est très similaire au passage par référence, mais pas la même chose. En C ++, cependant, vous pouvez passer par référence.
Mason Wheeler
@Mason Wheeler pouvez-vous élaborer plus ou ajouter un lien, beacuase votre déclaration n'est pas claire pour moi car je ne suis pas un gourou C / C ++, juste un programmeur régulier, merci
Betlista
1
@Betlista: Avec passe par référence, vous pouvez écrire une routine de swap qui ressemble à ceci: temp := a; a := b; b := temp;Et quand elle reviendra, les valeurs de aet bseront échangées. Il n'y a aucun moyen de faire ça en C; vous devez faire passer des pointeurs vers aet bet la swaproutine doit agir sur les valeurs auxquelles ils renvoient.
Mason Wheeler

Réponses:

58

Passer par valeur est souvent plus sûr que passer par référence, car vous ne pouvez pas modifier accidentellement les paramètres de votre méthode / fonction. Cela simplifie l'utilisation du langage, puisque vous n'avez pas à vous soucier des variables que vous attribuez à une fonction. Vous savez qu'ils ne seront pas changés et c'est souvent ce à quoi vous vous attendez .

Toutefois, si vous souhaitez modifier les paramètres, vous devez avoir une opération explicite pour rendre cela clair (passer un pointeur). Cela forcera tous vos appelants à effectuer l'appel légèrement différemment ( &variableen C), ce qui explicite le fait que le paramètre de variable peut être modifié.

Alors maintenant, vous pouvez supposer qu'une fonction ne changera pas votre paramètre de variable, sauf si cela est explicitement marqué (en vous obligeant à passer un pointeur). Il s’agit d’une solution plus sûre et plus propre que l’alternative: supposons que tout puisse modifier vos paramètres, à moins qu’ils ne le disent spécifiquement.

Oleksi
la source
4
+1 Les tableaux en décomposition en pointeurs constituent une exception notable, mais l'explication générale est bonne.
dasblinkenlight
6
@dasblinkenlight: les tableaux sont un point sensible dans C :(
Matthieu M.
55

Call-by-value et call-by-reference sont des techniques de mise en œuvre qui avaient été confondues avec les modes de passage de paramètres il y a longtemps.

Au début, il y avait Fortran. FORTRAN ne disposait que d’appels par référence, car les sous-programmes devaient pouvoir modifier leurs paramètres et les cycles de calcul étaient trop coûteux pour permettre plusieurs modes de passage de paramètres.

ALGOL a proposé appel par nom et appel par valeur. Call-by-value était pour des choses qui n'étaient pas supposées être changées (paramètres d'entrée). Appel par nom était pour les paramètres de sortie. Call-by-name s'est avéré être une marmite majeure, et ALGOL 68 l'a abandonné.

PASCAL a fourni appel par valeur et appel par référence. Cela ne permettait pas au programmeur d'indiquer au compilateur qu'il transmettait un objet volumineux (généralement un tableau) par référence, afin d'éviter de détruire la pile de paramètres, mais que l'objet ne devait pas être modifié.

PASCAL a ajouté des pointeurs au lexique de conception de langage.

C a fourni appel par valeur et simulé appel par référence en définissant un opérateur kludge pour renvoyer un pointeur sur un objet arbitraire en mémoire.

Les langues suivantes ont copié le C, principalement parce que les concepteurs n’avaient jamais rien vu d’autre. C'est probablement pourquoi appel par valeur est si populaire.

C ++ a ajouté un kludge au-dessus du C kludge pour fournir un appel par référence.

Maintenant, en tant que résultat direct appel par valeur vs appel par référence vs appel par pointeur-kludge, C et C ++ (programmeurs) ont des maux de tête horribles avec les pointeurs const et les pointeurs vers const (lecture seule) objets.

Ada a réussi à éviter tout ce cauchemar.

Ada n'a pas explicitement appel par valeur vs appel par référence. Au lieu de cela, Ada a dans les paramètres (qui peuvent être lus mais non écrits), les paramètres out (qui DOIVENT être écrits avant de pouvoir être lus) et les paramètres out, qui peuvent être lus et écrits dans n'importe quel ordre. Le compilateur décide si un paramètre particulier est passé par valeur ou par référence: il est transparent pour le programmeur.

John R. Strohm
la source
1
+1 C’est la seule réponse que j’ai vue ici jusqu’à présent qui réponde réellement à la question, qu’elle ait un sens et qu’elle ne l’utilise pas comme une excuse transparente pour un élan en faveur de la PF.
Mason Wheeler
11
+1 pour "les langues ultérieures ont copié le C, principalement parce que les concepteurs n’avaient jamais rien vu d’autre". Chaque fois que je vois une nouvelle langue avec des constantes octales à préfixe 0, je meurs un peu à l'intérieur.
librik
4
Cela pourrait être la première phrase de la Bible de la programmation: In the beginning, there was FORTRAN.
1
En ce qui concerne ADA, si une variable globale est transmise à un paramètre in / out, le langage empêchera-t-il un appel imbriqué de l'examiner ou de le modifier? Sinon, je penserais qu'il y aurait une nette différence entre les paramètres in / out et ref. Personnellement, j'aimerais que les langages prennent explicitement en charge toutes les combinaisons de "in / out / in + out" et "par valeur / par référence / non-soin", afin que les programmeurs puissent offrir un maximum de clarté quant à leur intention, mais optimiseurs aurait une flexibilité maximale dans la mise en œuvre [tant que cela concordait avec l'intention du programmeur].
Supercat
2
@Neikos Il y a aussi le fait que le 0préfixe est incohérent avec tous les autres préfixes non-base dix. Cela peut être un peu excusable en C (et la plupart des langages compatibles avec les sources, par exemple C ++, Objective C), si octal était la seule base alternative lors de son introduction, mais lorsque les langages plus modernes utilisent les deux 0xet 0dès le début, cela leur donne simplement un aspect médiocre. pensé.
8bittree
13

Passer par référence permet d’obtenir de très subtils effets secondaires non désirés qu'il est très difficile, voire impossible, de retracer lorsqu'ils commencent à provoquer le comportement imprévu.

Pass par valeur, en particulier final, staticou constles paramètres d’entrée font disparaître toute cette classe de bugs.

Les langages immuables sont encore plus déterministes et plus faciles à raisonner et à comprendre ce qui se passe et ce qui devrait sortir d'une fonction.


la source
7
C'est l'une de ces choses que vous avez découvertes après avoir essayé de déboguer du code VB «ancien», où tout est référencé par défaut.
jfrankcarr
Peut-être est-ce simplement dû au fait que j'ai un passé différent, mais je ne sais pas de quel type «d'effets secondaires involontaires très subtils il est très difficile d'être presque impossible à retracer» dont vous parlez. Je suis habitué à Pascal, où valeur par défaut est la valeur par défaut, mais référence par référence peut être utilisée en marquant explicitement le paramètre (essentiellement le même modèle que C #) et je n'ai jamais eu ce problème pour moi. Je peux voir en quoi cela poserait problème dans "Ancient VB" où By-Ref est la valeur par défaut, mais lorsque l'option "By-Ref" est activée, cela vous fait penser à cela pendant que vous l'écrivez.
Mason Wheeler
4
@MasonWheeler, qu'en est-il des débutants qui viennent derrière vous et copiez simplement ce que vous avez fait sans le comprendre et commencez à manipuler cet État de manière indirecte, le monde réel se passe tout le temps. Moins d'indirection est une bonne chose. Immuable est le meilleur.
1
@MasonWheeler Les exemples d'alias simples, tels que les Swaphacks qui n'utilisent pas de variable temporaire, bien qu'ils ne soient pas susceptibles de poser problème eux-mêmes, montrent à quel point un exemple plus complexe peut être difficile à déboguer.
Mark Hurd
1
@MasonWheeler: L'exemple classique de FORTRAN, qui a conduit à dire "Les variables ne le sont pas, les constantes ne le sont pas", serait de transmettre une constante à virgule flottante telle que 3.0 à une fonction qui modifie le paramètre transmis. Pour toute constante à virgule flottante transmise à une fonction, le système créerait une variable "masquée" initialisée avec la valeur appropriée et pouvant être transmise à des fonctions. Si la fonction ajoute 1.0 à son paramètre, un sous-ensemble arbitraire de 3.0 dans le programme pourrait devenir soudainement 4.0.
Supercat
8

Pourquoi tant de langues sont passées par valeur?

Le point essentiel de la division de gros programmes en petits sous-programmes est que vous pouvez raisonner indépendamment des sous-programmes. Le passage par référence casse cette propriété. (Tout comme l'état mutable partagé.)

Même les langues où vous avez une manipulation de pointeur explicite, comme C, sont toujours passées par valeur (vous pouvez les passer par référence mais ce n'est pas le comportement par défaut).

En fait, C est toujours passé par valeur, jamais par référence. Vous pouvez prendre une adresse de quelque chose et la transmettre, mais l'adresse sera toujours transmise par valeur.

Quel est l'avantage de cela, pourquoi tant de langues sont-elles passées par des valeurs et pourquoi d'autres sont-elles passées par référence ?

L’utilisation de référence par référence a deux raisons principales:

  1. simuler plusieurs valeurs de retour
  2. Efficacité

Personnellement, je pense que # 1 est faux: c'est presque toujours une excuse pour la mauvaise conception d'API et / ou de langage:

  1. Si vous avez besoin de plusieurs valeurs de retour, ne les simulez pas, utilisez simplement un langage qui les prend en charge.
  2. Vous pouvez également simuler plusieurs valeurs de retour en les regroupant dans une structure de données légère telle qu'un tuple. Cela fonctionne particulièrement bien si le langage prend en charge la correspondance de modèle ou la liaison de déstructuration. Par exemple Ruby:

    def foo
      # This is actually just a single return value, an array: [1, 2, 3]
      return 1, 2, 3
    end
    
    # Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
    one, two, three = foo
    
  3. Souvent, vous n'avez même pas besoin de plusieurs valeurs de retour. Par exemple, l'un des modèles courants est que le sous-programme renvoie un code d'erreur et que le résultat réel est réécrit par référence. Au lieu de cela, vous devriez simplement lever une exception si l'erreur est inattendue ou renvoyer un Either<Exception, T>si l'erreur est attendue. Une autre méthode consiste à renvoyer un booléen indiquant si l'opération a réussi et à renvoyer le résultat réel par référence. De nouveau, si l’échec est inattendu, vous devriez plutôt émettre une exception. Si l’échec est attendu, par exemple lors de la recherche d’une valeur dans un dictionnaire, vous devez renvoyer un Maybe<T>.

Le passage par référence peut s'avérer plus efficace que le passage par valeur, car vous n'avez pas à copier la valeur.

(Je comprends que Haskell est passé par référence bien que je ne sois pas sûr).

Non, Haskell n'est pas référencé. Ce n'est pas non plus valeur par passe. Pass-by-reference et pass-by-value sont deux stratégies d'évaluation strictes, mais Haskell n'est pas stricte.

En fait, la spécification Haskell ne spécifie aucune stratégie d'évaluation particulière. La plupart des implémentations Hakell utilisent une combinaison d'appel par nom et d'appel par besoin (une variante de l'appel par nom avec memoization), mais la norme ne l'exige pas.

Notez que même pour les langages stricts, il n’a pas de sens de faire la distinction entre pass-by-reference et pass-by-value pour les langages fonctionnels, car la différence entre eux ne peut être observée que si vous modifiez la référence. Par conséquent, l'implémenteur est libre de choisir entre les deux, sans casser la sémantique du langage.

Jörg W Mittag
la source
9
"Si vous avez besoin de plusieurs valeurs de retour, ne les simulez pas, utilisez simplement un langage qui les prend en charge." C'est un peu étrange à dire. Il dit en gros "si votre langue peut faire tout ce dont vous avez besoin, mais une fonctionnalité dont vous aurez probablement besoin dans moins de 1% de votre code - mais vous en aurez quand même besoin pour ce 1% - ne peut pas être réalisée dans De manière particulièrement propre, votre langue ne convient pas à votre projet et vous devez tout réécrire dans une autre langue. " Désolé, mais c'est tout simplement ridicule.
Mason Wheeler
+1 Je suis tout à fait d’accord avec ce point. L’intérêt de diviser de gros programmes en petits sous-programmes est que vous pouvez raisonner de manière indépendante sur les sous-programmes. Le passage par référence casse cette propriété. (Comme le fait l'état mutable partagé.)
Rémi
3

Le comportement varie en fonction du modèle d'appel du langage, du type d'arguments et des modèles de mémoire du langage.

Avec les types natifs simples, le passage par valeur vous permet de transmettre la valeur par les registres. Cela peut être très rapide car la valeur n'a pas besoin d'être chargée de la mémoire ni sauvegardée. Une optimisation similaire est également possible simplement en réutilisant la mémoire utilisée par l'argument une fois que l'appelé est terminé, sans risquer de gâcher la copie de l'objet de l'appelant. Si l'argument était un objet temporaire, vous en auriez probablement enregistré une copie complète (C ++ 11 rend ce type d'optimisation encore plus évident avec la nouvelle référence de droite et sa sémantique de déplacement).

Dans de nombreux langages OO (C ++ est plus une exception dans ce contexte), vous ne pouvez pas transmettre d'objet par valeur. Vous êtes obligé de le transmettre par référence. Cela rend le code polymorphe par défaut et est plus en ligne avec la notion d'instances propre à OO. De plus, si vous voulez passer par valeur, vous devez en faire une copie vous-même, en reconnaissant le coût de la performance qui a généré cette action. Dans ce cas, la langue choisit pour vous l’approche la plus susceptible de vous donner les meilleures performances.

Dans le cas de langages fonctionnels, je suppose que passer par valeur ou référence est simplement une question d’optimisation. Étant donné que les fonctions de ce langage sont pures et donc exempt d’effets secondaires, il n’ya vraiment aucune raison de copier la valeur, sauf pour la vitesse. Je suis même presque sûr que ce langage partage souvent la même copie d'objets avec les mêmes valeurs, une possibilité disponible uniquement si vous utilisiez une sémantique de référence pass (const). Python a également utilisé cette astuce pour les chaînes entières et communes (comme les méthodes et les noms de classe), en expliquant pourquoi les entiers et les chaînes sont des objets constants en Python. Cela permet également d’optimiser à nouveau le code, en permettant par exemple une comparaison de pointeur au lieu de comparaison de contenu, et une évaluation paresseuse de certaines données internes.

Fabien Ninoles
la source
2

Vous pouvez passer une expression par valeur, c'est naturel. Passer une expression par référence (temporaire) est ... bizarre.

Herby
la source
1
De plus, passer une expression par référence temporaire peut conduire à de mauvais bugs (dans un langage avec énoncé), quand vous le changez heureusement (puisqu'il est juste temporaire), mais ensuite il se retourne contre vous lorsque vous passez une variable +0 au lieu de foo.
Herby
2

Si vous passez par référence, alors vous travaillez toujours avec des valeurs globales, avec tous les problèmes des globals (à savoir la portée et les effets secondaires non voulus).

Les références, tout comme les globales, sont parfois bénéfiques, mais elles ne devraient pas être votre premier choix.

Jmoreno
la source
3
Je suppose que vous vouliez dire passer par référence dans la première phrase?
jk.