[nem-pl] Uwagi różne
Kamil Skalski
nazgul at omega.pl
Fri Feb 20 11:02:12 CET 2004
On Friday 20 of February 2004 00:26, Marcin 'Qrczak' Kowalczyk wrote:
> dobre. Scheme ma dobre założenia systemu syntax-case (parsowanie źródła
> w abstrakcyjne drzewko przed rozwinięciem makr; higiena domyślnie,
> łamalna przez funkcję zamieniającą goły symbol w identyfikator koloru
> wziętego z kontekstu użycia makra), trochę gorsza realizacja (język i
> metajęzyk w tej samej przestrzeni nazw; niewygodna składnia przez
> wciśnięcie wszystkiego w sexpry; samo syntax-case niestandardowe,
> a sensowne traktowanie metapoziomów - opisane w
> <http://www.cs.utah.edu/plt/publications/macromod.pdf> - jeszcze
> bardziej niestandardowe).
Dzięki, poczytam, tym bardziej że właśnie brakuje mi innego spojrzenia niż
odziedziczone po Meta-Haskellu.
>
> > W tej chwili wyzwaniem dla nas jest wymyślenie czegoś ładnego do operacji
> > na definicjach typów:
>
> Bo macie statyczny system typów :-)
Miałem na myśli API/składnię, bo jak to będzie działać i jak zaimplementować
to wiemy (pewne elementy, tylko Michał wie ;) ale to nie szkodzi)
Statyczne typowanie nie jest tutaj problemem, to znaczy makra na deklaracjach
muszą się wykonać przed całkowitym otypowaniem tych deklaracji (bo po tym
będą już w stanie tylko odczytywać, a nie zmieniać te deklaracje, co zresztą
też może jest do ominięcia, ale trzebaby wtedy retypować jakieś części
proramu).
>
> > mamy klasy Produkt, ProduktDuzy, .... i chcemy napisać makro, które doda
> > do nich metodę DumpToFile zapisującą wartości pól klasy gdzieś tam.
>
> U mnie to jest funkcja, a nie makro. To nic, że trochę mniej efektywna.
Hm, zatem masz funkcję, która w runtime potrafi zlistować pola klasy z ich
typami?
> macro MakeDumperFor (c) : decl {
> def l = c.Fields ();
> def f = <[ f ]>; // gensym
> <[
> add_methods_for $c {
> // Nie wiem, jak u Was można dodawać metody do klasy po jej
> // zdefiniowaniu, jeśli w ogóle można. Jeśli nie można, to
> // język jest ograniczony.
> def DumpToFile ($f : file) : void {
> $(uzywamy l)
> }
> }
> ]>
> }
Hmm, ty chciałbyś stworzyć kopię c z nową metodą (tak wnioskuję tez z tego co
napisałeś o skutkach ubocznych). Poza tym add_methods_for jest tu też jakimś
makrem, które tworzy klasę z czymś nowym - w zasadzie możnaby się obyć bez
otaczających <[ .. ]> i przerzucić je do środka add_methods_for (c) {.... },
bo i nasze makro i add_methods powinny się wykonać w tej samej kompilacji.
>
> Trzeba pilnować, co jest na poziomie programu, a co na metapoziomie.
> O ile pobieranie informacji o klasie jest tu konieczne na metapoziomie,
> jeśli chcemy generować przystosowany do danego zestawu pól kod, o tyle
> byłoby niepokojące, jeśli dodanie metody do klasy polegałoby na skutku
> ubocznym wykonania odpowiedniego makra zamiast na wyprodukowaniu przez
> makro jakiejś deklaracji.
Nie widzę powodu, dla którego byłoby to niepokojące :D To znaczy rozumiem, że
masz podejście purysty funkcjonalnego, ale szczególnie w tej sytuacji nie
widzę powodów - i tak nowa klasa powinna przekryć starą, więc jaka jest
różnica czy zmienimy starą (dodając metodę), czy stworzymy nową która
przykryje ją... Aha, no i mam wrażenie (choć mogę się mylić), że ty byś
chciał tą deklarację dopiero w runtime wygenerować (tak chyba robi Ruby -
językom skryptowym NIE, statycznemu typowniu TAK)
Generalnie nasza idea jest taka, że marka zmieniają deklaracje przy których są
napisane, podczas kompilacji:
[MakeFileDumper] class Produkt { .... }
> Chyba że .NET to wyklucza, wtedy pewnie tak
> jak piszesz.
.Net nie ma tu nic do rzeczy - my go tylko używamy do generacji kodu - system
makr nie zmienił by się zasadniczo (może poza dynamicznym ładowaniem
binarek makr) gdybyśmy generowali Jave, JVM, ELF.
PS. co jest u Ciebie generowane?
> > <[ def $("g" : var) (x) { x + "aa" }; $("g" : var) ("bb") ]> a tutaj 'g'
> > zostanie wklejone w wynikowy kod takie jakim jest.
>
> To znaczy złapie które wystąpienia "g" ze źródła / innych makr? Jeśli
> wszystkie, to higiena jest iluzją i nigdy nie można być pewnym, że
> identyfikator użyty lokalnie w rozwinięciu makra będzie prywatny dla
> tego rozwinięcia. Trzeba kolorować na podstawie jakiegoś identyfikatora.
Chyba mnie źle zrozumiałeś - pokazałem metodę na bezpośrednie
wpisanie jakiegoś symbolu z generowany kod. Ktoś np. pisze duży program i wie,
że 'mySuperObject' jest czymś znanym w miejscu gdzie wystąpi wygenerowany kod
i nie zostanie przez nic złapane, to może sobie pozwolić na niehigieniczne
tego wstawienie (nie rozumiem, dlaczego piszesz, że 'nigdy nie można być
pewnym').
Mój przykład z definicją lokalnej funkcji nie był może dobry... tym bardziej
że powinien tak wyglądać:
def g = Macros.NewSymbol ();
<[ def $(g : var) (x) { x + "aa" }; $(g : var) ("bb") ]>
Oczywiście g jest unikalny dla całego kompilowanego programu (w tej chwili
wszystko jest kompilowane jednocześnie, a jak nie będzie to się to jakoś
rozwiąże, żeby nie było konfliktów)
>
> Co ma z kolei inny problem, AFAIK nieunikniony - jeśli jedno makro
> niehigienicznie wprowadza jakiś identyfikator, a inne rozwija się do
> użycia tego makra, to ten identyfikator będzie łapany tylko w treści
> makra, a nie w kodzie, który go używa. Czasem to jest to, o co nam
> chodzi, a czasem nie. Jeśli nie, to trzeba go ręcznie przeprowadzić do
> nowego kontekstu, tzn. w tym drugim makrze też go jawnie niehigiencznie
> wprowadzić jako alias na ten identyfikator wprowadzony przez pierwsze
> makro.
Generalnie nie rozumiem.... ale wydaje mi się, że obecność NewSymbol
rozwiązuje wszystkie problemy, o których piszesz. Generalnie niehigieniczność
(a raczej potrzeba mycia rąk samemu) przydaje się w sytucajach takich jak:
macro PrintTuple (tup, size : int)
{
def symbols = array (size);
mutable pvars <- [];
for (mutable i <- size - 1; i >= 0; i <- i - 1) {
symbols[i] <- Macros.NewSymbol ();
pvars <- <[ pattern: $(symbols[i] : var) ]> :: pvars;
};
mutable exps <- [];
for (mutable i <- size - 1; i >= 0; i <- i - 1)
exps <- <[ System.Console.WriteLine ($(symbols[i] : var)) ]> :: exps;
exps <- <[ def (.. $pvars) = $tup ]> :: exps;
<[ {.. $exps } ]>
}
czasami można sobie wyobrazić dwa makra współpracujące przez korzystanie z
umieszczanych gdzieś globalnych, wspólnych identyfikatorów. Nadal nie widzę
tu żadnych problemów i taki design wydaje mi się sensowny - jeśli chcesz
automatycznej higieny, to musisz wszystkie wystąpienia symbolu użyć w tym
samym quotowaniu, w którym go zadeklarowałeś, bo trudno aby
def x = <[ def y = 1; ]>
def z = <[ y ]>
mogło się automatycznie zhigienizować.
> > to u nich jest całkiem sensowny kod, a wg nas to totalne pomiszanie
> > języka z meta-językiem,
>
> Zgadzam się, ale nie słyszałem, żeby oni coś takiego chcieli (nie
> czytałem ich dokładnie ani ostatnio).
U nich jest tak, że $(...) oznacza "uruchom to co w środku podczas
kompilacji". Muszą zatem pisać $(printf "%d %s") 5 "aa", zamiast jak my
printf ("%d %s", 5, "aa"). Potem zaś piszą, że jeśli mamy <[ x ]> i x jest
zadeklarowaną lokalnie zmienną, to jest ona "liftowana" i działa to w sumie
identycznie jak <[ $(x) ]>. IMHO mają to niespójne i bardzo zależne od tego
ile ktoś popełnił błędów w pisaniu kodu (komunikaty będą mylące).
Kamil
More information about the devel-pl
mailing list