Por que a ligação não é um recurso nativo na maioria dos idiomas?


11

IMHO vincular uma variável a outra variável ou expressão é um cenário muito comum em matemática. De fato, no começo, muitos estudantes acham que o operador de atribuição (=) é algum tipo de ligação. Mas na maioria dos idiomas, a ligação não é suportada como um recurso nativo. Em alguns idiomas como C #, a ligação é suportada em alguns casos, com algumas condições preenchidas.

Mas IMHO implementar isso como um recurso nativo foi tão simples quanto alterar o seguinte código -

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

para isso-

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

Ou seja, colocar a instrução de ligação como atribuições após cada instrução alterar os valores de qualquer variável contida na expressão no lado direito. Depois disso, será possível aparar instruções redundantes (ou otimização na montagem após a compilação).

Portanto, por que não é suportado nativamente na maioria dos idiomas. Especialmente na família C de idiomas?

Atualizar:

De opiniões diferentes, acho que devo definir essa "ligação" proposta com mais precisão -

  • Esta é uma maneira de vincular. Somente soma é vinculada a a + b, não ao contrário.
  • O escopo da ligação é local.
  • Depois que a ligação é estabelecida, ela não pode ser alterada. Ou seja, uma vez que a soma é vinculada a a + b, a soma sempre será a + b.

Espero que a idéia esteja mais clara agora.

Atualização 2:

Eu só queria esse recurso de P # . Espero que esteja lá no futuro.


14
Possivelmente porque qualquer desenvolvedor de compilador que tentou adicionar esse recurso ao C foi caçado e morto.
Pete Wilson #

Estou falando de ligação unidirecional (direita para esquerda), não bidirecional. A ligação sempre afetará apenas uma variável.
21411 Gulshan

2
Pelo que vale, esse tipo de ponto de vista programático está em ascensão: programação reativa. O que você está descrevendo também é incorporado por programas de planilhas como o Excel, que são essencialmente ligação de dados (ou programação reativa) em esteróides.
Mike Rosenblum

11
Pode não ser um recurso da "maioria" das linguagens de programação, mas é um recurso da linguagem de programação mais popular : Excel.
Jörg W Mittag

2
Graças às árvores de expressão, isso já é possível em C #. Eu escrevi no blog sobre isso ontem: happynomad121.blogspot.com/2013/01/…
HappyNomad 02/02

Respostas:


9

Você está confundindo programação com matemática. Nem mesmo a programação funcional é inteiramente matemática, embora empreste muitas idéias e as transforme em algo que possa ser executado e usado para programação. A programação imperativa (que inclui a maioria das linguagens inspiradas em C, com exceções notáveis ​​como JavaScript e os adições mais recentes ao C #) não tem quase nada a ver com matemática, então por que essas variáveis ​​devem se comportar como variáveis ​​em matemática?

Você deve considerar que isso nem sempre é o que você deseja. Muitas pessoas são mordidas por fechamentos criados em loops especificamente porque os fechamentos mantêm a variável, e não uma cópia do seu valor em algum momento, ou seja, for (i = 0; i < 10; i++) { var f = function() { return i; }; /* store f */ }cria dez fechamentos que retornam 9. Portanto, você precisa oferecer suporte para os dois lados - o que significa o dobro do custo no "orçamento de complexidade" e mais um operador. Possivelmente também incompabilidades entre código usando isso e código não usando isso, a menos que o sistema de tipos seja sofisticado o suficiente (mais complexidade!).

Além disso, implementar isso de forma eficaz é muito difícil. A implementação ingênua adiciona uma sobrecarga constante a todas as atribuições, o que pode aumentar rapidamente em programas imperativos. Outras implementações podem atrasar as atualizações até que a variável seja lida, mas isso é significativamente mais complexo e ainda possui sobrecarga, mesmo quando a variável nunca é lida novamente. Um compilador suficientemente inteligente pode otimizar ambos, mas compiladores suficientemente inteligentes são raros e exigem muito esforço para serem criados (observe que nem sempre é tão simples quanto no seu exemplo, especialmente quando as variáveis ​​têm amplo escopo e o multithreading entra em jogo!).

Note que a programação reativa é basicamente sobre isso (até onde eu sei), então ela existe. Simplesmente não é tão comum nas linguagens de programação tradicionais. E aposto que alguns dos problemas de implementação listados no parágrafo anterior foram resolvidos.


Eu acho que você tem 3 pontos - 1) Não é uma programação de estilo imperativa. Atualmente, a maioria das línguas não se limita a alguns paradigmas. Eu não acho que isso esteja fora desse paradigma. É uma boa lógica. 2) complexidade. Você mostrou que muitas coisas mais complexas já são suportadas. Por que não este? Eu sei que os operadores que quase não têm uso estão sendo suportados. E esse é um recurso totalmente novo. Então, como pode haver um problema de compatibilidade. Não estou mudando ou limpando alguma coisa. 3) Implementação difícil. Eu acho que os compiladores atuais já têm a capacidade de otimizar isso.
21811 Gulshan

1
@Gulshan: (1) Nenhum paradigma de programação é matemática. O FP é próximo, mas o FP é relativamente raro e as linguagens de FP impuras com variáveis ​​mutáveis ​​também não tratam essas variáveis ​​como se fossem da matemática. O único paradigma em que isso existe é a programação reativa, mas a programação reativa não é conhecida ou usada amplamente (nas linguagens de programação tradicionais). (2) Tudo tem algum custo de complexidade e um valor. Isso tem um custo bastante alto e o IMHO tem relativamente pouco valor, exceto em alguns domínios, por isso não é a primeira coisa que eu adicionaria ao meu idioma, a menos que ele visasse especificamente esses domínios.

Por que não ter mais uma ferramenta em nossa caixa? Uma vez que uma ferramenta esteja lá, as pessoas farão uso dela. Uma pergunta. Se alguma linguagem como C ou C ++ quiser implementar esse recurso e pedir sua opinião, você diria: "Não dê. Porque será muito difícil para você e as pessoas vão estragar tudo".
Gulshan

@Gulshan: Em relação a (3): nem sempre é (para não dizer, raramente) tão fácil quanto no seu exemplo. Coloque-o no escopo global e de repente você precisará de otimizações de tempo de link. Em seguida, adicione links dinâmicos e você não pode nem fazer nada em tempo de compilação. Você precisa de um compilador muito inteligente e de um tempo de execução muito inteligente, incluindo o JIT, para fazê-lo bem. Considere também a quantidade de tarefas em todos os programas não triviais. Nem você nem eu podemos escrever esses componentes. Existem no máximo algumas pessoas que podem e estão interessadas nesse assunto. E eles podem ter coisas melhores para fazer.

@Gulshan: Eu pediria que eles não adicionassem recursos à besta incrivelmente complexa que C ++ já é, ou pediria que não tentassem usar C para coisas além da programação do sistema (que não é um dos domínios em que isso é muito útil). ) Além disso, sou a favor de novos recursos empolgantes, mas apenas quando há o orçamento de complexidade suficiente (muitos idiomas sempre esgotam os deles) e o recurso é útil para o que o idioma pretende fazer - como eu disse antes, existem apenas alguns domínios em que isso é útil.

3

Ele se encaixa muito mal na maioria dos modelos de programação. Representaria um tipo de ação à distância completamente descontrolada, na qual seria possível destruir o valor de centenas ou milhares de variáveis ​​e campos de objetos, fazendo uma única atribuição.


Então, sugiro que você faça uma regra de que a variável vinculada, ou seja, o lado esquerdo, não será alterada de nenhuma outra maneira. E o que eu estou falando é apenas unidirecional, não bidirecional. Se você seguir minha pergunta, você pode ver. Portanto, nenhuma outra variável será afetada.
21811 Gulshan

Isso não importa. Toda vez que você escrever para aou b, é preciso considerar seu impacto sobre cada lugar que sumé usado, e cada lugar que você lê sum, você precisa considerar o que ae bestão fazendo. Para casos não triviais, isso pode ser complicado, especialmente se a expressão real vinculada a sumpuder mudar no tempo de execução.
jprete

Eu recomendaria a regra de que a expressão de ligação não pode ser alterada depois que a ligação for concluída. Mesmo a atribuição será impossível. Será como uma semi-constante, uma vez ligada a uma expressão. Isso significa que, uma vez que a soma esteja vinculada a a + b, ela sempre será a + b, durante o restante do programa.
Gulshan

3

Você sabe, eu tenho essa sensação incômoda de que a programação reativa pode ser legal em um ambiente Web2.0. Por que o sentimento? Bem, eu tenho essa página que é basicamente uma tabela que muda o tempo todo em resposta aos eventos onClick da célula da tabela. E os cliques nas células geralmente significam alterar a classe de todas as células em uma coluna ou linha; e isso significa infinitas voltas de getRefToDiv () e similares para encontrar outras células relacionadas.

IOW, muitas das ~ 3000 linhas de JavaScript que escrevi não fazem nada além de localizar objetos. Talvez a programação reativa possa fazer tudo isso a baixo custo; e com uma enorme redução nas linhas de código.

O que vocês acham disso? Sim, percebo que minha tabela possui muitos recursos semelhantes a planilhas.


3

Eu acho que o que você está descrevendo é chamado de planilha:

A1=5
B1=A1+1
A1=6

... avaliando B1retornos 7.

EDITAR

A linguagem C às vezes é chamada de "assembly portátil". É uma linguagem imperativa, enquanto planilhas etc. são linguagens declarativas. Dizer B1=A1+1e esperar B1reavaliar quando você muda A1é definitivamente declarativo. Linguagens declarativas (das quais linguagens funcionais são um subconjunto) são geralmente consideradas linguagens de nível superior, porque estão mais distantes do funcionamento do hardware.

Em uma nota relacionada, linguagens de automação como lógica ladder são tipicamente declarativas. Se você escrever um degrau de lógica que diz output A = input B OR input Cque vai reavaliar essa afirmação constantemente e Apode mudar sempre que Bou Cmuda. Outras linguagens de automação, como o diagrama de blocos de funções (com o qual você pode estar familiarizado se tiver usado o Simulink), também são declarativas e são executadas continuamente.

Alguns equipamentos de automação (incorporados) são programados em C e, se for um sistema em tempo real, provavelmente possui um loop infinito que reexecuta a lógica repetidas vezes, semelhante à maneira como a lógica ladder é executada. Nesse caso, dentro do loop principal, você pode escrever:

A = B || C;

... e como está sendo executado o tempo todo, torna-se declarativo. Aserá constantemente reavaliado.


3

C, C ++, Objetivo-C:

Os blocos fornecem o recurso de encadernação que você está procurando.

No seu exemplo:

soma: = a + b;

você está definindo suma expressão a + bem um contexto em que ae bsão variáveis ​​existentes. Você pode fazer exatamente isso com um "bloco" (também conhecido como fechamento, também conhecido como expressão lambda) em C, C ++ ou Objective-C com as extensões da 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

Isso define sumum bloco que retorna a soma de a e b. O __blockespecificador de classe de armazenamento indica isso ae bpode ser alterado. Dado o exposto, podemos executar o seguinte código:

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());

e obtenha a saída:

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

A única diferença entre o uso de um bloco e a "ligação" que você propõe é o par vazio de parênteses sum(). A diferença entre sume sum()é a diferença entre uma expressão e o resultado dessa expressão. Observe que, como nas funções, os parênteses não precisam estar vazios - os blocos podem receber parâmetros da mesma forma que as funções.


2

C ++

Atualizado para ser genérico. Parametrizado nos tipos de retorno e entrada. Pode fornecer qualquer operação binária que satisfaça os tipos parametrizados. O código calcula o resultado sob demanda. Ele tenta não recalcular os resultados, se conseguir se safar. Retire isso se for indesejável (por causa de efeitos colaterais, porque os objetos contidos são grandes, por causa do que seja.)

#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;
}

Embora não responda à pergunta, eu gosto da ideia que você apresentou. Pode haver uma versão mais genérica?
Gulshan #

@Gulshan: Atualizado
Thomas Eding
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.