Pourquoi la liaison n'est pas une fonctionnalité native dans la plupart des langues?


11

IMHO lier une variable à une autre variable ou une expression est un scénario très courant en mathématiques. En fait, au début, de nombreux étudiants pensent que l'opérateur d'affectation (=) est une sorte de liaison. Mais dans la plupart des langues, la liaison n'est pas prise en charge en tant que fonctionnalité native. Dans certains langages comme C #, la liaison est prise en charge dans certains cas avec certaines conditions remplies.

Mais à mon humble avis, la mise en œuvre de cette fonctionnalité en tant que fonctionnalité native était aussi simple que la modification du code suivant:

int a,b,sum;
sum := a + b;
a = 10;
b = 20;
a++;

pour ça-

int a,b,sum;
a = 10;
sum = a + b;
b = 20;
sum = a + b;
a++;
sum = a + b;

Cela signifie placer l'instruction de liaison en tant qu'affectations après chaque instruction modifiant les valeurs de l'une des variables contenues dans l'expression à droite. Après cela, le découpage des instructions redondantes (ou l'optimisation dans l'assemblage après la compilation) fera l'affaire.

Alors, pourquoi il n'est pas pris en charge nativement dans la plupart des langues. Spécialement dans la famille C des langues?

Mettre à jour:

De différentes opinions, je pense que je devrais définir plus précisément ce "contraignant" proposé.

  • Ceci est contraignant à sens unique. Seule la somme est liée à a + b, pas l'inverse.
  • La portée de la liaison est locale.
  • Une fois la liaison établie, elle ne peut plus être modifiée. Autrement dit, une fois que sum est lié à a + b, sum sera toujours a + b.

J'espère que l'idée est plus claire maintenant.

Mise à jour 2:

Je voulais juste cette fonctionnalité P # . J'espère qu'il sera là à l'avenir.


14
Peut-être parce que tout développeur de compilateur qui a essayé d'ajouter cette fonctionnalité à C a été traqué et abattu.
Pete Wilson

Je parle de la liaison unidirectionnelle (de droite à gauche), pas de la réciprocité. La liaison n'affectera toujours qu'une seule variable.
Gulshan

2
Pour ce que ça vaut, ce genre de point de vue programmatique est à la hausse: la programmation réactive. Ce que vous décrivez est également incarné par des programmes de feuille de calcul tels qu'Excel, qui sont essentiellement des liaisons de données (ou programmation réactive) sur les stéroïdes.
Mike Rosenblum

11
Il pourrait ne pas être une caractéristique de « la plupart » des langages de programmation, mais il est une caractéristique de la plus populaire langage de programmation: Excel.
Jörg W Mittag

2
Grâce aux arbres d'expression, cela est déjà possible en C #. J'ai blogué à ce sujet hier: happynomad121.blogspot.com/2013/01/…
HappyNomad

Réponses:


9

Vous confondez la programmation avec les mathématiques. Même la programmation fonctionnelle n'est pas entièrement mathématique, même si elle emprunte de nombreuses idées et les transforme en quelque chose qui peut être exécuté et utilisé pour la programmation. La programmation impérative (qui inclut la plupart des langages inspirés de C, à l'exception notable de JavaScript et des ajouts les plus récents à C #) n'a presque rien à voir avec les mathématiques, alors pourquoi ces variables devraient-elles se comporter comme des variables en mathématiques?

Vous devez considérer que ce n'est pas toujours ce que vous voulez. Beaucoup de gens sont mordus par des fermetures créées en boucles, spécifiquement parce que les fermetures gardent la variable, pas une copie de sa valeur à un moment donné, c'est-à-dire for (i = 0; i < 10; i++) { var f = function() { return i; }; /* store f */ }créent dix fermetures qui reviennent 9. Vous auriez donc besoin de prendre en charge les deux façons - ce qui signifie deux fois le coût du «budget de complexité» et encore un autre opérateur. Peut-être aussi des incompabilités entre le code utilisant ceci et le code ne l'utilisant pas, à moins que le système de type soit suffisamment sophistiqué (plus de complexité!).

En outre, la mise en œuvre efficace de cette méthode est très difficile. L'implémentation naïve ajoute un surcoût constant à chaque mission, qui peut s'additionner rapidement dans les programmes impératifs. D'autres implémentations peuvent retarder les mises à jour jusqu'à ce que la variable soit lue, mais c'est beaucoup plus complexe et il y a toujours un surcoût même lorsque la variable n'est jamais relue. Un compilateur suffisamment intelligent peut optimiser les deux, mais les compilateurs suffisamment intelligents sont rares et demandent beaucoup d'efforts pour créer (notez que ce n'est pas toujours aussi simple que dans votre exemple, surtout lorsque les variables ont une large portée et que le multithreading entre en jeu!).

Notez que la programmation réactive est fondamentalement à ce sujet (pour autant que je sache), donc elle existe. Ce n'est tout simplement pas si courant dans les langages de programmation traditionnels. Et je parie que certains des problèmes d'implémentation que j'ai énumérés dans le paragraphe précédent sont résolus.


Je pense que vous avez 3 points - 1) Ce n'est pas une programmation de style impérative. De nos jours, la plupart des langues ne se limitent pas à certains paradigmes. Je ne pense pas que ce soit hors de ce style de paradigme est une bonne logique. 2) Complexité. Vous avez montré que de nombreuses choses plus complexes sont déjà prises en charge. Pourquoi pas celui-là? Je sais que les opérateurs n'ayant pratiquement aucune utilité sont pris en charge. Et c'est une fonctionnalité totalement nouvelle. Alors, comment peut-il y avoir le problème de compatibilité. Je ne change ni n'efface quelque chose. 3) Mise en œuvre difficile. Je pense que les compilateurs actuels ont déjà la capacité d'optimiser cela.
Gulshan

1
@Gulshan: (1) Aucun paradigme de programmation n'est mathématique. FP est proche, mais FP est relativement rare et les langages FP impurs avec des variables mutables ne traitent pas ces variables comme si elles provenaient des mathématiques non plus. Le seul paradigme où cela existe est la programmation réactive, mais la programmation réactive n'est pas connue ou largement utilisée (dans les langages de programmation traditionnels). (2) Tout a un coût de complexité et une valeur. Cela a un coût assez élevé et à mon humble avis relativement peu de valeur sauf dans quelques domaines, donc ce n'est pas la première chose que j'ajouterais à ma langue à moins qu'elle ne cible spécifiquement ces domaines.

Pourquoi ne pas avoir un outil de plus dans notre boîte? Une fois qu'un outil est là, les gens s'en serviront. Une question. Si un langage comme C ou C ++ veut implémenter cette fonctionnalité et demander votre avis, diriez-vous, "Ne le donnez pas. Parce que ce sera trop difficile pour vous et que les gens vont gâcher cela."?
Gulshan le

@Gulshan: Concernant (3): Ce n'est pas toujours (pour ne pas dire, rarement) aussi facile que dans votre exemple. Mettez-le à l'échelle mondiale et vous avez soudainement besoin d'optimisations de temps de liaison. Ajoutez ensuite un lien dynamique et vous ne pouvez même rien faire au moment de la compilation. Vous avez besoin d'un compilateur très intelligent et d' un runtime très intelligent incluant JIT pour bien le faire. Tenez également compte du nombre d'affectations dans chaque programme non trivial. Ni vous ni moi ne pouvons écrire ces composants. Il y a au plus quelques personnes qui peuvent et sont intéressées par ce sujet. Et ils pourraient avoir de meilleures choses à faire.

@Gulshan: Je leur demanderais de ne pas ajouter de fonctionnalité à la bête incroyablement complexe C ++ est déjà, ou je leur demanderais de ne pas essayer d'utiliser C pour des choses au-delà de la programmation système (qui n'est pas l'un des domaines où cela est très utile ). En dehors de cela, je suis tout pour les nouvelles fonctionnalités passionnantes, mais seulement lorsqu'il reste suffisamment de budget de complexité (de nombreuses langues épuisent toujours la leur) et que la fonctionnalité est utile pour ce que la langue est censée faire - comme je l'ai dit précédemment, il y a ne sont que quelques domaines où cela est utile.

3

Il correspond très mal à la plupart des modèles de programmation. Cela représenterait une sorte d'action à distance complètement incontrôlée, dans laquelle on pourrait détruire la valeur de centaines ou de milliers de variables et de champs d'objets en faisant une seule affectation.


Ensuite, je suggérerais de faire une règle selon laquelle la variable liée, c'est-à-dire le côté gauche, ne sera pas modifiée d'une autre manière. Et ce dont je parle, c'est seulement d'une manière contraignante, pas bidirectionnelle. Si vous suivez ma question, vous pouvez voir. Ainsi, aucune autre variable ne sera affectée.
Gulshan

Ça n'a pas d'importance. Chaque fois que vous écrivez à aou b, vous devez tenir compte de son impact sur chaque endroit sumutilisé et chaque endroit que vous lisez, sumvous devez tenir compte de ce que vous faites aet de ce que vous bfaites. Pour les cas non triviaux, cela pourrait devenir compliqué, surtout si l'expression réelle liée à sumpeut changer au moment de l'exécution.
jprete

Je recommanderais la règle selon laquelle l'expression de liaison ne peut pas être modifiée une fois la liaison effectuée. Même la cession sera impossible. Ce sera comme une semi-constante une fois liée à une expression. Cela signifie qu'une fois la somme liée à a + b, elle sera toujours a + b, pendant le reste du programme.
Gulshan

3

Tu sais, j'ai ce sentiment persistant que la programmation réactive peut être cool dans un environnement Web2.0. Pourquoi ce sentiment? Eh bien, j'ai cette page qui est principalement un tableau qui change tout le temps en réponse aux événements onClick de cellule de tableau. Et les clics sur les cellules signifient souvent de changer la classe de toutes les cellules d'un col ou d'une ligne; et cela signifie des boucles sans fin de getRefToDiv (), etc., pour trouver d'autres cellules apparentées.

IOW, la plupart des ~ 3000 lignes de JavaScript que j'ai écrites ne font que localiser des objets. Peut-être que la programmation réactive pourrait faire tout cela à peu de frais; et à une énorme réduction des lignes de code.

Qu'en pensez-vous? Oui, je remarque que ma table possède de nombreuses fonctionnalités de type tableur.


3

Je pense que ce que vous décrivez s'appelle une feuille de calcul:

A1=5
B1=A1+1
A1=6

... puis évaluer les B1rendements 7.

ÉDITER

Le langage C est parfois appelé «assemblage portable». C'est un langage impératif, alors que les feuilles de calcul, etc., sont des langages déclaratifs. Dire B1=A1+1et espérer B1réévaluer lorsque vous changez A1est définitivement déclaratif. Les langages déclaratifs (dont les langages fonctionnels sont un sous-ensemble) sont généralement considérés comme des langages de niveau supérieur, car ils sont plus éloignés du fonctionnement du matériel.

Sur une note connexe, les langages d'automatisation comme la logique à relais sont généralement déclaratifs. Si vous écrivez une ligne de logique qui dit output A = input B OR input Cqu'elle va réévaluer cette déclaration en permanence et Apeut changer à chaque fois Bou Cchanger. D'autres langages d'automatisation comme Function Block Diagram (que vous connaissez peut-être si vous avez utilisé Simulink) sont également déclaratifs et s'exécutent en continu.

Certains équipements d'automatisation (intégrés) sont programmés en C, et s'il s'agit d'un système en temps réel, il a probablement une boucle infinie qui réexécute la logique encore et encore, semblable à la façon dont la logique à relais s'exécute. Dans ce cas, à l'intérieur de votre boucle principale, vous pouvez écrire:

A = B || C;

... et comme il s'exécute tout le temps, il devient déclaratif. Aseront constamment réévalués.


3

C, C ++, Objective-C:

Les blocs offrent la fonction de reliure que vous recherchez.

Dans votre exemple:

somme: = a + b;

vous définissez suml'expression a + bdans un contexte où aet bsont des variables existantes. Vous pouvez faire exactement cela avec un "bloc" (aka fermeture, aka expression lambda) en C, C ++ ou Objective-C avec les extensions d' Apple (pdf):

__block int a = 0, b = 0;           // declare a and b
int (^sum)(void);                   // declare sum
sum = ^(void){return a + b;};       // sum := a + b

Cela définit sumun bloc qui renvoie la somme de a et b. Le __blockspécificateur de classe de stockage indique cela aet bpeut changer. Compte tenu de ce qui précède, nous pouvons exécuter le code suivant:

printf("a=%d\t b=%d\t sum=%d\n", a, b, sum());
a = 10;
printf("a=%d\t b=%d\t sum=%d\n", a, b, sum());
b = 32;
printf("a=%d\t b=%d\t sum=%d\n", a, b, sum());
a++;
printf("a=%d\t b=%d\t sum=%d\n", a, b, sum());

et obtenez la sortie:

a=0      b=0     sum=0
a=10     b=0     sum=10
a=10     b=32    sum=42
a=11     b=32    sum=43

La seule différence entre l'utilisation d'un bloc et la "liaison" que vous proposez est la paire de parenthèses vide sum(). La différence entre sumet sum()est la différence entre une expression et le résultat de cette expression. Notez que, comme pour les fonctions, les parenthèses ne doivent pas être vides - les blocs peuvent prendre des paramètres comme les fonctions.


2

C ++

Mis à jour pour être générique. Paramétré sur les types de retour et d'entrée. Peut fournir n'importe quelle opération binaire satisfaisant les types paramétrés. Le code calcule le résultat à la demande. Il essaie de ne pas recalculer les résultats s'il peut s'en tirer. Retirez ceci si cela n'est pas souhaitable (à cause des effets secondaires, parce que les objets contenus sont grands, à cause de quoi que ce soit.)

#include <iostream>

template <class R, class A, class B>
class Binding {
public:
    typedef R (*BinOp)(A, B);
    Binding (A &x, B &y, BinOp op)
        : op(op)
        , rx(x)
        , ry(y)
        , useCache(false)
    {}
    R value () const {
        if (useCache && x == rx && y == ry) {
            return cache;
        }
        x = rx;
        y = ry;
        cache = op(x, y);
        useCache = true;
        return cache;
    }
    operator R () const {
        return value();
    }
private:
    BinOp op;
    A &rx;
    B &ry;
    mutable A x;
    mutable B y;
    mutable R cache;
    mutable bool useCache;
};

int add (int x, int y) {
    return x + y;
}

int main () {
    int x = 1;
    int y = 2;
    Binding<int, int, int> z(x, y, add);
    x += 55;
    y *= x;
    std::cout << (int)z;
    return 0;
}

Bien que cela ne réponde pas à la question, j'aime l'idée que vous avez présentée. Peut-il y avoir une version plus générique?
Gulshan

@Gulshan: Mise à jour
Thomas Eding
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.