Dlaczego wiązanie nie jest funkcją natywną w większości języków?

11

IMHO powiązanie zmiennej z inną zmienną lub wyrażeniem jest bardzo częstym scenariuszem w matematyce. W rzeczywistości na początku wielu uczniów uważa, że ​​operator przypisania (=) jest rodzajem wiązania. Ale w większości języków wiązanie nie jest obsługiwane jako funkcja natywna. W niektórych językach, takich jak C #, wiązanie jest obsługiwane w niektórych przypadkach, przy spełnieniu niektórych warunków.

Ale IMHO zaimplementowanie tego jako funkcji natywnej było tak proste, jak zmiana następującego kodu:

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

do tego-

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

Oznacza umieszczenie instrukcji wiązania jako przypisań po każdej instrukcji zmieniającej wartości dowolnej zmiennej zawartej w wyrażeniu po prawej stronie. Po tym nastąpi przycięcie zbędnych instrukcji (lub optymalizacja w zestawie po kompilacji).

Dlaczego więc nie jest obsługiwany natywnie w większości języków. Szczególnie w rodzinie języków C?

Aktualizacja:

Z różnych opinii uważam, że powinienem bardziej precyzyjnie zdefiniować proponowane „wiążące”

  • Jest to wiążące w jedną stronę. Tylko suma jest związana z + +, a nie odwrotnie.
  • Zakres powiązania jest lokalny.
  • Po ustanowieniu powiązania nie można go zmienić. Oznacza to, że gdy suma zostanie przypisana do + b, suma zawsze będzie a + b.

Mam nadzieję, że pomysł jest teraz jaśniejszy.

Aktualizacja 2:

Chciałem tylko tej funkcji P # . Mam nadzieję, że będzie tam w przyszłości.

Gulszan
źródło
14
Być może dlatego, że każdy programista kompilatora, który próbował dodać tę funkcję do C, został ścigany i zastrzelony.
Pete Wilson,
Mówię o wiązaniu jednokierunkowym (od prawej do lewej), a nie dwukierunkowym. Wiązanie będzie zawsze dotyczyło tylko jednej zmiennej.
Gulshan
2
Co do wartości, ten rodzaj programowego punktu widzenia rośnie: programowanie reaktywne. To, co opisujesz, jest również zawarte w programach arkuszy kalkulacyjnych, takich jak Excel, które zasadniczo wiążą dane (lub programowanie reaktywne) na sterydach.
Mike Rosenblum,
11
Może to nie być funkcja „większości” języków programowania, ale jest to funkcja najpopularniejszego języka programowania: Excel.
Jörg W Mittag
2
Dzięki drzewom wyrażeń jest to już możliwe w języku C #. Wczoraj o tym napisałem na blogu: happynomad121.blogspot.com/2013/01/…
HappyNomad

Odpowiedzi:

9

Mylisz programowanie z matematyką. Nawet funkcjonalne programowanie nie jest całkowicie matematyczne, chociaż zapożycza wiele pomysłów i zamienia je w coś, co można wykonać i wykorzystać do programowania. Programowanie imperatywne (które obejmuje większość języków inspirowanych językiem C, godne uwagi wyjątki to JavaScript i nowsze dodatki do C #) nie ma prawie nic wspólnego z matematyką, więc dlaczego te zmienne powinny zachowywać się jak zmienne matematyczne?

Musisz wziąć pod uwagę, że nie zawsze jest to, czego chcesz. Tak wiele osób jest gryzionych przez zamknięcia utworzone w pętlach, ponieważ zamknięcia zachowują zmienną, a nie kopię jej wartości w pewnym momencie, tj. for (i = 0; i < 10; i++) { var f = function() { return i; }; /* store f */ }Tworzy dziesięć zamknięć, które powracają 9. Trzeba więc wspierać w obie strony - co oznacza dwukrotność kosztu „budżetu złożoności” i jeszcze jednego operatora. Być może także niezgodności między kodem używającym tego a kodem nieużywającym tego, chyba że system typów jest wystarczająco zaawansowany (większa złożoność!).

Również skuteczne wdrożenie tego jest bardzo trudne. Naiwna implementacja powoduje stałe obciążenie każdego zadania, które może szybko zsumować się w programach imperatywnych. Inne implementacje mogą opóźniać aktualizacje, dopóki zmienna nie zostanie odczytana, ale jest to znacznie bardziej złożone i wciąż ma narzut, nawet jeśli zmienna nigdy nie zostanie odczytana ponownie. Wystarczająco inteligentny kompilator może zoptymalizować oba, ale wystarczająco inteligentne kompilatory są rzadkie i wymagają dużego wysiłku, aby je utworzyć (pamiętaj, że nie zawsze jest to tak proste jak w twoim przykładzie, szczególnie gdy zmienne mają szeroki zakres i wielowątkowość wchodzi w grę!).

Zauważ, że programowanie reaktywne jest w zasadzie o tym (o ile mogę powiedzieć), więc istnieje. To po prostu nie jest tak powszechne w tradycyjnych językach programowania. Założę się, że niektóre problemy z implementacjami wymienione w poprzednim akapicie zostały rozwiązane.


źródło
Myślę, że masz 3 punkty - 1) To nie jest programowanie w stylu imperatywnym. Obecnie większość języków nie ogranicza się do paradygmatów. Nie sądzę, że to nie z tego stylu paradygmatu jest dobrą logiką. 2) Złożoność. Pokazałeś, że wiele bardziej skomplikowanych rzeczy jest już obsługiwanych. Dlaczego nie ten? Wiem, że obsługiwani są operatorzy, którzy prawie nie korzystają. I to jest zupełnie nowa funkcja. Jak więc może wystąpić problem ze zgodnością. Nie zmieniam ani nie wycieram. 3) Trudne wdrożenie. Myślę, że obecne kompilatory mają już możliwość zoptymalizowania tego.
Gulshan
1
@Gulshan: (1) Żaden paradygmat programowania nie jest matematyką. FP jest bliskie, ale FP jest stosunkowo rzadkie, a nieczyste języki FP ze zmiennymi zmiennymi nie traktują tych zmiennych tak, jakby były z matematyki. Jedynym paradygmatem, w którym istnieje, jest programowanie reaktywne, ale programowanie reaktywne nie jest znane ani powszechnie stosowane (w tradycyjnych językach programowania). (2) Wszystko ma pewien koszt złożoności i wartość. Ma to dość wysoki koszt i relatywnie niewielką wartość IMHO, z wyjątkiem kilku domen, więc nie jest to pierwsza rzecz, którą dodałbym do mojego języka, chyba że byłby specjalnie ukierunkowany na te domeny.
Dlaczego nie ma jeszcze jednego narzędzia w naszym pudełku? Gdy narzędzie będzie dostępne, ludzie skorzystają z niego. Pytanie. Jeśli jakiś język, taki jak C lub C ++, chce wdrożyć tę funkcję i poprosić o opinię, powiedz: „Nie dawaj jej. Ponieważ będzie to dla ciebie zbyt trudne i ludzie będą się z tym bałaganić”.
Gulshan,
@Gulshan: Odnośnie (3): Nie zawsze jest to (nie mówiąc rzadko) tak łatwe jak w twoim przykładzie. Wprowadź go w zakres globalny i nagle potrzebujesz optymalizacji czasu łącza. Następnie dodaj dynamiczne łączenie, a nie będziesz mógł nic zrobić podczas kompilacji. Potrzebujesz bardzo inteligentnego kompilatora i bardzo sprytnego środowiska wykonawczego, w tym JIT, aby zrobić to dobrze. Weź również pod uwagę liczbę przypisań w każdym nietrywialnym programie. Ani ty, ani ja nie możemy napisać tych składników. Jest co najwyżej kilka osób, które mogą i są zainteresowane tym tematem. I mogą mieć lepsze rzeczy do zrobienia.
@Gulshan: Poprosiłbym ich, aby nie dodawali funkcji do niesamowicie złożonej bestii, którą już C ++ już jest, lub prosiłbym, aby nie próbowali używać C do rzeczy wykraczających poza programowanie systemowe (co nie jest jedną z domen, w których jest to bardzo przydatne ). Poza tym jestem za ekscytującymi nowymi funkcjami, ale tylko wtedy, gdy pozostanie wystarczający budżet na złożoność (wiele języków zawsze wyczerpuje swój) i funkcja ta jest przydatna do tego, do czego służy język - jak powiedziałem wcześniej, tam to tylko kilka domen, w których jest to przydatne.
3

Bardzo słabo pasuje do większości modeli programowania. Byłby to rodzaj całkowicie niekontrolowanej akcji na odległość, w której można zniszczyć wartość setek lub tysięcy zmiennych i pól obiektowych, wykonując jedno przypisanie.

jprete
źródło
Następnie zasugerowałbym regułę, że zmienna związana, tj. Lewa strona, nie zostanie zmieniona w żaden inny sposób. A to, o czym mówię, jest tylko wiążące w jedną stronę, a nie dwukierunkowe. Jeśli podążysz za moim pytaniem, możesz to zobaczyć. Tak więc nie wpłynie to na żadną inną zmienną.
Gulshan
To nie ma znaczenia Za każdym razem, gdy piszesz do alub b, musisz rozważyć jego wpływ na każde sumużywane miejsce i każde miejsce, które czytasz sum, musisz zastanowić się, co ai brobisz. W przypadkach innych niż trywialne może się to skomplikować, szczególnie jeśli rzeczywiste wyrażenie, do którego się odnosi, summoże się zmienić w czasie wykonywania.
jprete,
Poleciłbym regułę, że wyrażenie wiązania nie może zostać zmienione po zakończeniu wiązania. Nawet przydział będzie niemożliwy. Po związaniu z wyrażeniem będzie jak półstała. Oznacza to, że gdy suma zostanie przypisana do + b, zawsze będzie to + +, przez resztę programu.
Gulshan
3

Wiem, mam dokuczliwe przeczucie, że reaktywne programowanie może być fajne w środowisku Web2.0. Skąd to uczucie? Cóż, mam tę jedną stronę, która jest głównie tabelą, która zmienia się cały czas w odpowiedzi na zdarzenia onClick w komórkach tabeli. Kliknięcia komórek często oznaczają zmianę klasy wszystkich komórek w kolumnie lub wierszu; a to oznacza niekończące się pętle getRefToDiv () i tym podobnych, aby znaleźć inne powiązane komórki.

IOW, wiele z ~ 3000 wierszy JavaScript, które napisałem, nic nie robi, tylko lokalizuje obiekty. Może programowanie reaktywne może to wszystko zrobić przy niewielkich kosztach; i przy ogromnej redukcji linii kodu.

Co o tym myślicie? Tak, zauważam, że mój stół ma wiele funkcji podobnych do arkusza kalkulacyjnego.

Pete Wilson
źródło
3

Myślę, że to, co opisujesz, nazywa się arkusz kalkulacyjny:

A1=5
B1=A1+1
A1=6

... następnie oceniając B1zwroty 7.

EDYTOWAĆ

Język C jest czasem nazywany „przenośnym zestawem”. Jest to język imperatywny, podczas gdy arkusze kalkulacyjne itp. Są językami deklaratywnymi. Mówienie B1=A1+1i oczekiwanie B1ponownej oceny po zmianie A1jest zdecydowanie deklaratywne. Języki deklaratywne (których podzbiórami są języki funkcjonalne) są ogólnie uważane za języki wyższego poziomu, ponieważ są bardziej oddalone od sposobu działania sprzętu.

W pokrewnej uwadze języki automatyzacji, takie jak logika drabinkowa, są zazwyczaj deklaratywne. Jeśli napiszesz szczebel logiki, który mówi, output A = input B OR input Cże będzie stale oceniać to stwierdzenie i Amoże się zmieniać za każdym razem Blub Czmieniać. Inne języki automatyzacji, takie jak schemat bloków funkcyjnych (który możesz znać, jeśli korzystałeś z Simulink), są również deklaratywne i działają w sposób ciągły.

Niektóre (wbudowane) urządzenia automatyzacyjne są zaprogramowane w C, a jeśli jest to system czasu rzeczywistego, prawdopodobnie ma nieskończoną pętlę, która ponownie wykonuje logikę w kółko, podobnie jak logika drabinkowa. W takim przypadku w głównej pętli możesz napisać:

A = B || C;

... a ponieważ cały czas działa, staje się deklaratywny. Abędzie stale poddawany ponownej ocenie.

Scott Whitlock
źródło
3

C, C ++, Objective-C:

Bloki zapewniają funkcję wiązania, której szukasz.

W twoim przykładzie:

suma: = a + b;

jesteś ustawienie sumdo wyrażenia a + bw kontekście, w którym ai bsą istniejące zmienne. Możesz to zrobić za pomocą „bloku” (aka closure, aka lambda expression) w C, C ++ lub Objective-C z rozszerzeniami 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

Ustawia sumsię na blok, który zwraca sumę aib. Specyfikator __blockklasy pamięci wskazuje to ai bmoże się zmienić. Biorąc powyższe pod uwagę, możemy uruchomić następujący kod:

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

i uzyskaj wynik:

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

Jedyną różnicą między użyciem bloku a proponowanym „wiązaniem” jest pusta para nawiasów w sum(). Różnica pomiędzy suma sum()stanowi różnicę między ekspresji i wyniku tej ekspresji. Zauważ, że podobnie jak w przypadku funkcji, nawiasy nie muszą być puste - bloki mogą przyjmować parametry tak jak funkcje.

Caleb
źródło
2

C ++

Zaktualizowano, aby był ogólny. Sparametryzowane na typach zwrotów i wejściowych. Może dostarczyć dowolną operację binarną spełniającą sparametryzowane typy. Kod oblicza wynik na żądanie. Stara się nie przeliczać wyników, jeśli da sobie radę. Wyjmij to, jeśli jest to niepożądane (z powodu skutków ubocznych, ponieważ zawarte obiekty są duże, z jakiegokolwiek powodu).

#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;
}
Thomas Eding
źródło
Chociaż to nie odpowiada na pytanie, podoba mi się twój pomysł. Czy może istnieć bardziej ogólna wersja?
Gulshan
@Gulshan: Zaktualizowano
Thomas Eding