Pytanie Mathematica: co to jest programowanie symboliczne?


Jestem wielkim fanem Stephena Wolframa, ale z pewnością nie jest on nieśmiały, by wykonać swój własny róg. W wielu odniesieniach wychwala Mathematica jako inny symboliczny paradygmat programowania. Nie jestem użytkownikiem Mathematica.

Moje pytania brzmią: co to jest programowanie symboliczne? A jak się ma do języków funkcjonalnych (takich jak Haskell)?


76
2017-12-13 16:28


pochodzenie




Odpowiedzi:


Możesz myśleć o symbolicznym programowaniu Mathematica jako systemu wyszukiwania i zamiany, w którym programujesz, określając reguły wyszukiwania i zamiany.

Na przykład możesz określić następującą regułę

area := Pi*radius^2;

Następnym razem area, zostanie zastąpiony przez Pi*radius^2. Załóżmy teraz, że definiujesz nową regułę

radius:=5

Teraz, kiedy tylko używasz radius, zostanie przepisany na nowo 5. Jeśli oceniasz area zostanie przepisany na nowo Pi*radius^2 co wyzwala regułę przepisywania dla radius a dostaniesz Pi*5^2 jako wynik pośredni. Ten nowy formularz uruchomi wbudowaną regułę przepisywania dla ^ operacja, więc wyrażenie zostanie ponownie przepisane Pi*25. W tym momencie przepisywanie zostaje zatrzymane, ponieważ nie ma odpowiednich reguł.

Możesz emulować funkcjonalne programowanie przy użyciu reguł zastępowania jako funkcji. Na przykład, jeśli chcesz zdefiniować funkcję, która dodaje, możesz to zrobić

add[a_,b_]:=a+b

Teraz add[x,y] zostanie przepisany na x+y. Jeśli chcesz, aby dodać tylko do numeru a, b, możesz zamiast tego zrobić

add[a_?NumericQ, b_?NumericQ] := a + b

Teraz, add[2,3] zostanie przepisany na 2+3 używając swojej reguły, a następnie do 5 za pomocą wbudowanej reguły dla +, natomiast add[test1,test2] pozostaje bez zmian.

Oto przykład interaktywnej reguły zastępczej

a := ChoiceDialog["Pick one", {1, 2, 3, 4}]
a+1

Tutaj, a otrzymuje brzmienie ChoiceDialog, który następnie zostaje zastąpiony numerem, który wybrał użytkownik w wyświetlonym oknie dialogowym, co powoduje, że obie wielkości są numeryczne i uruchamia regułę wymiany dla +. Tutaj, ChoiceDialog jako wbudowaną regułę zastępowania zgodnie z linią "replace ChoiceDialog [niektóre rzeczy] z wartością przycisku, który użytkownik kliknął".

Reguły można definiować za pomocą warunków, które same muszą przejść przez przepisywanie przepisów w celu ich wytworzenia True lub False. Załóżmy na przykład, że wymyśliliście nową metodę rozwiązywania równań, ale myślicie, że działa ona tylko wtedy, gdy końcowy wynik waszej metody jest pozytywny. Możesz wykonać następującą regułę

 solve[x + 5 == b_] := (result = b - 5; result /; result > 0)

Tutaj, solve[x+5==20] otrzymuje brzmienie 15, ale solve[x + 5 == -20] pozostaje niezmieniony, ponieważ nie ma żadnej zasady, która ma zastosowanie. Warunkiem uniemożliwiającym zastosowanie tej reguły jest /;result>0. Ewaluator zasadniczo szuka potencjalnego wyjścia zastosowania reguły, aby zdecydować, czy ją zrealizować.

Osoba oceniająca Mathematica łapczywie przepisuje każdy wzór z jedną z zasad, które mają zastosowanie do tego symbolu. Czasami chcesz mieć dokładniejszą kontrolę, w takim przypadku możesz zdefiniować własne reguły i zastosować je ręcznie w ten sposób

myrules={area->Pi radius^2,radius->5}
area//.myrules

Zastosuje to zasady określone w myrules aż wynik przestanie się zmieniać. Jest to podobne do domyślnego ewaluatora, ale teraz możesz mieć kilka zestawów reguł i stosować je selektywnie. Bardziej zaawansowany przykład pokazuje, jak zrobić ewaluator Prolog-podobny, który przeszukuje sekwencje aplikacji reguł.

Jedna wada obecnej wersji Mathematica pojawia się, gdy trzeba użyć domyślnego oceniającego Mathematica (do skorzystania z Integrate, Solveitp.) i chcesz zmienić domyślną sekwencję oceny. Jest to możliwe, ale skomplikowanei lubię myśleć, że niektóre przyszłe implementacje programowania symbolicznego będą miały bardziej elegancki sposób kontrolowania sekwencji oceny


70
2017-12-13 22:12



@ Yaro Myślę, że rzecz staje się bardziej interesująca, gdy otrzymujesz reguły w wyniku działania funkcji (jak w rozwiązaniu Solve, DSolve itp.) - Dr. belisarius
Ale możesz myśleć Solve to po prostu kolejny zestaw reguł przepisywania. Kiedy podajesz równania, których Mathematica nie może rozwiązać Solve[hard_equations] pozostaje jako Solve[hard_equations] i możesz zdefiniować niestandardowy Solve zasada, która ma zastosowanie w tym przypadku. W tym przypadku, zgaduję, że używają /; Warunkiem jest zdefiniowanie wzorca dla "dowolnego równania, które można rozwiązać za pomocą metod w Mathematica", więc dla wbudowanych reguł wbudowanych twardych równań nie ma zastosowania i Solve pozostaje w oryginalnej formie - Yaroslav Bulatov
Myślę, że to zbyt skomplikowane, programy Mathematica to w zasadzie tylko zestawy reguł wymiany. Wykonywanie to proces stosowania istniejących reguł do wprowadzania, dopóki nie zostaną dopasowane żadne reguły - Yaroslav Bulatov
+1 Bardzo ładne, nierozwikłane wyjaśnienie braku rogów. Być może jedyną rzeczą, którą chciałbym dodać, jest to, że istnieją jotyla reguł i algorytmów już zawartych w jądrze, reprezentujących prawie wszystkie matematyczne biblioteki dostępne w większości języków, a także kilka innych. - Dr. belisarius
Simon, sam rachunek lambda to tylko jeden z systemów do przepisywania. Termin przepisywanie jest bardziej ogólnym podejściem niż jakikolwiek konkretny TRS. - SK-logic


Kiedy słyszę zdanie "programowanie symboliczne", LISP, Prolog i (tak) Mathematica od razu myślą. Chciałbym scharakteryzować symboliczne środowisko programistyczne jako takie, w którym wyrażenia używane do reprezentowania tekstu programu również staną się pierwotną strukturą danych. W rezultacie bardzo łatwo jest budować abstrakcje na abstrakcjach, ponieważ dane można łatwo przekształcić w kod i odwrotnie.

Mathematica intensywnie wykorzystuje tę zdolność. Jeszcze bardziej intensywny niż LISP i Prolog (IMHO).

Jako przykład programowania symbolicznego rozważ następującą sekwencję zdarzeń. Mam plik CSV, który wygląda tak:

r,1,2
g,3,4

Przeczytałem ten plik w:

Import["somefile.csv"]
--> {{r,1,2},{g,3,4}}

Czy dane wynikowe lub kod? To jest jedno i drugie. Są to dane, które wynikają z odczytania pliku, ale również jest to wyrażenie, które skonstruuje te dane. Jak się jednak okazuje kod, wyrażenie to jest obojętne, ponieważ wynik oceny jest po prostu sam.

Teraz stosuję transformację do wyniku:

% /. {c_, x_, y_} :> {c, Disk[{x, y}]}
--> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}

Bez zajmowania się szczegółami, wszystko, co się wydarzyło, jest takie Disk[{...}] został zawinięty wokół dwóch ostatnich liczb z każdej linii wejściowej. Rezultatem jest nadal dane / kod, ale nadal obojętne. Kolejna transformacja:

% /. {"r" -> Red, "g" -> Green}
--> {{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}

Tak, nadal nieruchome. Jednak niezwykłym zbiegiem okoliczności ten ostatni wynik jest właśnie listą prawidłowych dyrektyw w języku Mathematica, który jest wbudowany w język specyficzny dla grafiki. Ostatnia transformacja i wszystko zaczyna się dziać:

% /. x_ :> Graphics[x]
--> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]

W rzeczywistości nie zobaczysz tego ostatniego wyniku. W epickiej prezentacji cukru syntaktycznego Mathematica wyświetliłby obrazek czerwonych i zielonych kółek:

alt text

Ale zabawa nie kończy się na tym. Pod całym tym cukrem syntaktycznym wciąż mamy symboliczny wyraz. Mogę zastosować kolejną regułę transformacji:

% /. Red -> Black

alt text

Presto! Czerwone kółko stało się czarne.

Jest to ten rodzaj "pchania symboli", który charakteryzuje programowanie symboliczne. Zdecydowana większość programów Mathematica ma taki charakter.

Funkcjonalny a symboliczny

Nie będę szczegółowo omawiać różnic między programowaniem symbolicznym i funkcjonalnym, ale przedstawię kilka uwag.

Można spojrzeć na programowanie symboliczne jako odpowiedź na pytanie: "Co by się stało, gdybym próbował modelować wszystko, używając tylko transformacji ekspresji?" Funkcjonalne programowanie może być postrzegane jako odpowiedź na pytanie: "Co by się stało, gdybym próbował modelować wszystko za pomocą tylko funkcji?" Podobnie jak programowanie symboliczne, programowanie funkcjonalne ułatwia szybkie tworzenie warstw abstrakcji. Podany tutaj przykład można z łatwością odtworzyć na przykład w Haskell za pomocą funkcjonalnego podejścia do animacji reaktywnej. Funkcjonalne programowanie polega na składzie funkcji, funkcjach wyższego poziomu, kombinatorach - wszystkich fajnych rzeczach, które można wykonywać za pomocą funkcji.

Mathematica jest wyraźnie zoptymalizowana do programowania symbolicznego. Możliwe jest pisanie kodu w stylu funkcjonalnym, ale funkcje funkcjonalne w Mathematice są tak naprawdę tylko cienką okleiną na transformacje (i nieszczelną abstrakcją, patrz przypis poniżej).

Haskell jest wyraźnie zoptymalizowany pod kątem programowania funkcjonalnego. Możliwe jest napisanie kodu w symbolicznym stylu, ale spierdalalbym o to syntaktyczny reprezentacja programów i danych jest dość wyraźna, co sprawia, że ​​doświadczenie jest suboptymalne.

Uwagi końcowe

Podsumowując, opowiadam się za rozróżnieniem programowania funkcjonalnego (jak uosabia Haskell) i programowania symbolicznego (jak uosabia Mathematica). Myślę, że jeśli ktoś studiuje jedno i drugie, wówczas nauczy się znacznie więcej, niż tylko jeden - ostateczny test odrębności.


Przeciekająca funkcjonalna abstrakcja w Mathematica?

Tak, nieszczelny. Spróbuj tego, na przykład:

f[x_] := g[Function[a, x]];
g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]];
f[999]

Należycie zgłoszone i uznane przez WRI. Odpowiedź: unikaj użycia Function[var, body] (Function[body] jest ok).


72
2017-12-14 03:54



Czy WRI naprawdę radził ci unikać Function[var, body]? To dziwne, ponieważ jest zalecane w dokumentach ... - Simon
@Simon: Tak, mam e-mail od WRI, który stwierdza, że ​​jeśli martwię się semantyką funkcji zmieniającej się w zależności od tego, czy jakikolwiek dzwoniący w "łańcuchu połączeń" używa podobnego symbolu, powinienem unikać za pomocą Function[var, body]. Nie wyjaśniono, dlaczego nie można tego naprawić, ale spekuluję, że od tego czasu Function istnieje od 1.0, zmiana w jego zachowaniu spóźnia się w grze. Problem jest opisany (nieco) bardziej szczegółowo tutaj. - WReach
Przy poziomie ekspozycji jego elementów wewnętrznych w mma, nie jestem nawet pewien Function można by było wyleczyć, nawet w zasadzie - przynajmniej z obecną intrygującą semantyką Rule i RuleDelayed, które nie respektują wiązań wewnętrznych konstrukcji skopów, w tym samych siebie. Zjawisko to wydaje mi się bardziej związane z tą własnością Rule i RuleDelayed, a konkretnie do Function. Ale tak czy inaczej, zgadzam się, że zmiana jest teraz bardzo niebezpieczna. Szkoda, ponieważ Function[var,body] nie powinny być używane - takie błędy będą prawie niemożliwe do złapania w spore projekty. - Leonid Shifrin
@WReach Matematyka, który jest klonem FOSS Mathematica, nie ma nieszczelnego problemu z Funkcją! Po prostu to wypróbowałem. - M. Alaggan


Jak inni już wspomniano, Mathematica robi wiele przepisywania terminów. Może jednak Haskell nie jest najlepszym porównaniem, ale Czysty to przyjemny, funkcjonalny termin przepisywania języka (który powinien być znajomy dla osób z tłem Haskella). Może przeczytanie ich strony Wiki o przepisywaniu terminu rozwiąże kilka rzeczy:

http://code.google.com/p/pure-lang/wiki/Rewriting


9
2017-12-14 09:37



Semantycznie, Pure jest bardziej podobny do Schematu (dynamiczne pisanie, domyślnie do eagerowej oceny) lub OCaml (ma wyraźne komórki referencyjne). Ale składnia i biblioteki odzwierciedlają Haskella. - dubiousjim


Mathematica intensywnie używa przepisywania terminu. Język zapewnia specjalną składnię dla różnych form przepisywania, specjalne wsparcie dla reguł i strategii. Paradygmat nie jest "nowy" i, oczywiście, nie jest wyjątkowy, ale zdecydowanie jest na krawędzi tego "programowania symbolicznego", podobnie jak innych silnych graczy, takich jak Axiom.

Jeśli chodzi o porównanie z Haskellem, cóż, możesz tam przepisać, przy odrobinie pomocy odrzucić swoją bibliotekę, ale nie jest to tak łatwe, jak w dynamicznie napisanym Mathematica.


5
2017-12-13 18:23