[nem-pl] Uwagi różne

Marcin 'Qrczak' Kowalczyk qrczak at knm.org.pl
Sat Feb 21 01:56:32 CET 2004


W liście z pią, 20-02-2004, godz. 19:59, Kamil Skalski pisze:

> Zawsze alternatywą jest dziedziczenie, w końcu to OO. Tam możemy robić
> z klasami (za pomocą makr i ręcznie) co nam się podoba.

To, że statycznie typowane obiektowe modelowanie nie pozwala rozszerzać
interfejsu wcześniej zdefiniowanych typów, wystarcza, żebym nie zawracał
sobie tak ograniczonym paradygmatem głowy.

Dziedziczenie nie wystarczy. Nie będę robił podklasy stringa w celu
dodania nowej funkcji działającej na stringach ani w celu
zadeklarowania, że stringi implementują nowy interfejs, bo dane obiektu
się nie zmieniają - to stare stringi mają implementować nowe funkcje,
stosowalność tej funkcji nie jest cechą stringu jako takiego.

Zresztą w połowie języków z tej grupy i tak nie da się tego zrobić,
a z intami jest jeszcze gorzej. Można w Nemerle zrobić podklasę inta?
Najlepiej żeby obiekty tej klasy nie były większe od intów - nie dostają
nowych atrybutów, tylko nową funkcjonalność.

Sprawdza się, co ludzie mówili: .NET nadaje się do implementowania
skórek C#, a nie innych języków.

> Można najwyżej sobie wyobrazić makro, które
> przekształca każde List<int> , List<Foo>, itd. w ListHashable<Foo> jeśli Foo
> jest hashable. Byłby to jakiś automatyzm, ale z drugiej strony czy nie lepiej
> żeby użytkownik widział jakiego typu właściwie jest jego List<Foo>?

Funkcja, która w polimorficzny sposób operuje na listach, może tego
nie wiedzieć. I nie wystarczy jej, że ListHashable<Foo> jest podtypem
List<Foo>, bo mamy programowanie funkcyjne, kiedy listy są również
zwracane jako wynik, a nie tylko zmieniane w miejscu (zresztą tutaj
nie można zmieniać w miejscu - bardzo dobrze, u mnie też).

Jeśli taka funkcja nie korzysta akurat z haszowania, to nie powinna
musieć osobno uwzględniać typu haszowanej listy. Z drugiej strony, jeśli
produkuje listę stringów, które - jak to stringi - są haszowalne, to nie
ma dobrego usprawiedliwienia przed stratą haszowalności, poza tym - ten
model jest tak ograniczony i już.

> No ja zdaję sobie sprawę, że niehigienicznymi makrami można sobie zrobić
> krzywdę taką, że nawet się nie zorientuje człowiek o co chodzi.

Krzywda może być mniejsza albo większa, brak higieny nie musi od razu
oznaczać niekonsekwentnego i niebezpiecznego zachowania.

Wiele języków ma elementy, które zachowują się jak niehigieniczne makra,
głównie wprowadzając (a nie łapiąc) umówione identyfikatory: while i
inne pętle z breakiem i continuem, class z selfem/thisem i superem,
definicja funkcji z returnem. Byłoby dziwne, gdyby while z breakiem
można było użyć w głównym programie, a w makrze nie; byłoby również
dziwme, gdyby break wprowadzony przez wbudowane while zachowywał się
subtelnie inaczej niż break wprowadzony przez while-makro.

> Prawdę mówiąc, moglibyśmy w ogóle zabronić niehigieniczności, wywalając
> konstrukcję <[ $(id : var) ]>... bo właściwie nie widzę powodów żeby nie...

Można. Standard Scheme ma wyłącznie higieniczne makra, w formie reguł
przepisywania (z "pętlami", ale tylko powtarzającymi elementy list
z argumentów makr). Ale prawie każda implementacja Scheme ma bardziej
lub mniej ogólnie przyjęty system pozwalający na łamanie higieny
i wykonywanie kodu Scheme w makrach: czy to define-macro (jak defmacro
w Common Lispie, bez higieny), czy to syntax-case (porządny system
domyślnej higieny i kontrolowanego łamania higieny). Widać ludzie tego
potrzebowali i standardowe makra im nie wystarczały.

Dylan ma domyślnie higieniczne makra, z możliwością łamania higieny. Nie
wiem, jak ogólne i jak dobre. Możecie do niego zajrzeć też dlatego, że
jako jeden z nielicznych języków pozwala makrom na wprowadzanie własnych
produkcji gramatyki. Jest to bardzo skomplikowane i gramatyka wciąż musi
być mocno Dylano-podobna (jest dużo specjalnych reguł specyficznych dla
jego stylu składni), ale działa - na dodatek z higieną.

Każdy Lisp i Scheme, a także mój język, który mimo wszystko nie jest
lispem, traktuje składniową stronę makr prościej niż Dylam i Nemerle:
najpierw parsuje źródło w amorficzne drzewko, rozpoznaje nawiasy,
priorytety itp., a dopiero potem makra nadają węzłom znaczenie i
ustalają, że te nawiasy klamrowe będą oznaczać anonimową funkcję,
a tamte grupują ciało konstruktora typu.

Aha, nie słyszałem o makrach tego rodzaju, które byłyby statycznie
typowane. Template Haskell jednak wyglądają inaczej (z tym jawnym
zaznaczeniem, że rozwijamy makro, a nie wołamy funkcję). Chyba
wkraczacie na terytorium, na którym nikt nie był.

> Z drugiej strony, ostatecznie aż takiej wielkiej krzywdy się łatwo nie zrobi,
> bo najczęściej wszystko się wywali z jakimś niezrozumiałym błędem
> podczas kompilacji.

Którego nie będzie można poprawić inaczej niż rezygnując z używania
danego makra (np. while) w rozwinięciu innego makra, tylko rozpisując
go ręcznie. Tu nie chodzi o niespodzianki, tylko o dostępną
funkcjonalność.

> Moje pytanie jest w takim razie takie, czy wiesz jak uchronić się przed tymi
> zakręconymi przypadkami które opisujesz.

Bardzo z grubsza. Próbowałem opisać dwa listy temu, jak sobie wyobrażam
mój system, a Dybvig z innymi opisał implementację syntax-case. Sprawa
jest trudna i pewnie wymagała paru iteracji różnych systemów makr, zanim
ludzie doszli, jak to naprawdę powinno działać.

Na przykład jakaś implementacja Scheme reprezentowała kolory jako
pojedyncze liczby, a nie listy liczb, i przy rozwinięciu kawałka makra
nadawała wszystkim nazwom nowy, unikalny kolor - powoduje to zlepienie
nazw o tych samych symbolach, które wcześniej różniły się kolorami, co
w subtelny sposób psuje jakieś użycia makr w rozwinięciach innych makr.
Implementacja higienicznych makr nie jest łatwa i intuicyjna, ledwo
łapię to umysłem...

> > U mnie można to napisać. Ten błąd wyskoczy dopiero przy kompilacji
> > rozwinięcia makra - ale błędu nie będzie w ogóle, jeśli y będzie
> > wstawione w roli nazwy czegoś definiowanego, a wszystkie wystąpienia
> > w roli użycia będą w zakresie takiej definicji.
> 
> Często jest to tylko walka o to kiedy wyskoczy błąd - zawsze lepiej żeby
> podczas kompilacji makra, niż kompilacji użycia makra.

Nie zawsze. Na przykład nie chciałbym, żeby przy napisaniu 1/x wyskoczył
błąd kompilacji, że kompilator nie jest pewien, czy przypadkiem x nie
będzie 0. Błąd powinien wyskakiwać w czasie kompilacji tylko jeśli
faktycznie jest błędem, a nie tylko czymś, co może być użyte poprawnie
bądź nie.

> > Jeśli makro jest użyte przez inne makro, to mamy dwa miejsca, które
> > można uznać za kontekst użycia pierwszego makra:
> > 1. treść drugiego makra
> > 2. kontekst użycia drugiego makra
> >
> > Według Scheme i według mojego systemu łamanie higieny przez pierwsze
> > makro oznacza wsadzanie bądź pobieranie identyfikatorów z miejsca 1, ale
> > nie 2. Żeby makro forwardowało niehigieniczne identyfikatory, musi to
> > zrobić jawnie.
> 
> Tak i to jest pomysł, który musimy rozważyć. Bo w tej chwili makra występujące
> w quotowaniach nie są expandowane.

One nie są expandowane też przez Scheme i mój system. Co nie zmienia
faktu, że niehigiena pozostaje na miejscu. Bo łamanie higieny w Scheme
nie polega na kolorowaniu symboli kolorem głównego programu, tylko
kolorem miejsca użycia makra!!!

Patrz dokumentacja i przykłady użycia funkcji datum->syntax-object. Po
to trzeba jej podać jakiś identyfikator na wzór. Ona produkuje nazwę,
która udaje, że była wpisana w tym leksykalnym kontekście, co wzór. A u
mnie wzór sam będzie podany niejawnie (bo tak wygodniej, a mniejsza
ogólność akurat nie przeszkadza; w razie czego można przekazywać sobie
instancje funkcji implicit zamiast wzorów kolorów).

To działa. Jeszcze nie wiemy, że tam w środku jest jakieś niehigieniczne
makro, ale na wszelki wypadek pokolorowaliśmy nazwy z cytatu na nowy
kolor, żeby wszelkie permutacje tych nazw - czy to higieniczne, czy to
oszukańczo wprowadzone przez makra - łapały tylko nazwy pochodzące ze
swojego rejonu.

> Tylko czy to nie spowoduje jakichś innych problemów?

To, że niehigiena nie przedostaje się dalej? Zależy od punktu widzenia.
Czasem chcemy, żeby się przedostała (jeśli robimy repeat za pomocą while
i chcemy, żeby break działał po staremu), a czasem nie (jeśli robimy
while za pomocą lokalnej funkcji i wcale nie chcemy, żeby w ciele pętli
znalazło się return z funkcji - załóżmy dla potrzeby przykładu, że
funkcje niehigienicznie wprowadzają "return" tak jak w C).

Jedyne zdrowe rozwiązanie tego dylematu, jakie znam, to stwierdzenie:
niehigiena  dotyczy tylko miejsca bezpośredniego użycia makra, kropka.
Jeśli ktoś jest taki kozak, że pisze makro rozwijające się do
niehigienicznego makra, to sam w nim musi dodać forward nazwy:
   def $implicit.break = break;
i musi być świadomy, które nazwy mają przecieknąć. Nie ma możliwości
automatycznego przeciekania wszystkiego (poza sztuczką, którą opisałem
poprzednio, której się chyba nie poleca).

> Z taką funkcją niejawnie dostępną to my też tak musimy zorobić. Już teraz
> makro niejawnie ma dostępną zmienną "_N_ctx" czyli kontekst typowania.

Czyli "macro" jest niehigienicznym makrem. Oby _N_ctx było wprowadzane
na tych samych zasadach, co przez inne niehigieniczne makra, bo inaczej
ludzie się pogubią.

> Hmm, między liniami odczytuję algorytm taki, że zmienne kolorowane są
> w obrębie makra, a nie quotowania:
> <[ def x = 5; ]>
> <[ x ]> 
> jest poprawne, jeśli użyte w tym samym makrze.

Chyba Scheme'owe syntax-case tak nie robi (artykuł Dybviga jest do tego
prostopadły, bo on nie mówi o tym, jak są pisane makra, tylko że
dostarczają funkcji produkującej kawałek kodu ze wstawionymi
parametrami). Ja się z tym nie zgadzam, ktoś inny też to zauważył -
wątki na ten temat:
http://groups.google.com/groups?hl=pl&lr=&ie=UTF-8&oe=UTF-8&c2coff=1&frame=right&th=ebca9e36ed30ad21&seekm=q38kvvoovchhaq2bbhf7oqhasf3unjdss6%404ax.com
http://groups.google.com/groups?hl=pl&lr=&ie=UTF-8&oe=UTF-8&c2coff=1&frame=right&th=5bc7116453d53528&seekm=770d8f1a.0401071158.31a75371%40posting.google.com

Bywa, że kolorowanie na poziomie makra jest wygodniejsze, ale prowadzi
do paradoksów.

Wątek o higienicznych makrach z moim udziałem, AFAIR też poruszał tę
sprawę:
http://groups.google.com/groups?hl=pl&lr=&ie=UTF-8&oe=UTF-8&c2coff=1&frame=right&th=ab76e60b17426461&seekm=pan.2003.11.28.17.57.25.687295%40knm.org.pl
przy czym wtedy rozumiałem mniej niż teraz i niektóre moje hipotezy były
błędne.

-- 
   __("<         Marcin Kowalczyk
   \__/       qrczak at knm.org.pl
    ^^     http://qrnik.knm.org.pl/~qrczak/





More information about the devel-pl mailing list