[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